文章目錄
零碎知識點
題目
12219 - Common Subexpression Elimination
題目鏈接:UVA12219 公共表達式消除
思路:
代碼:
#include<iostream>
#include<algorithm>
#include<string>
#include<sstream>
#include<set>
#include<vector>
#include<stack>
#include<map>
#include<queue>
#include<deque>
#include<cstdlib>
#include<cstdio>
#include<cstring>
using namespace std;
const int mx = 60000;
int T, rnd, cnt;
char expr[mx * 5], *p;
int done[mx];
struct Node//子樹的結構體
{
string s;//存子樹字母
int left, right;
bool operator < (const Node&b)const//由於下面要用到map::count函數,所以必須要重載小於號
{
if (s != b.s)return s < b.s;
if (left != b.left)return left < b.left;//哈希相同則比較出現的順序
return right < b.right;
}
}node[mx];
map <Node, int>dict;//記錄子樹的編號
//爲所有的子樹編號
int solve()
{
int id = cnt++;//從0開始編號,將子樹映射爲編號
//初始化一個子樹
Node&u = node[id];
u.left = u.right = -1;
u.s = "";
while (isalpha(*p))//獲取子樹的根節點的字符串
{
u.s.push_back(*p);//將該子樹的字母放入s
p++;//向後掃描,遇到括號停止
}
if (*p == '(')//(L,R),遞歸處理左右子樹
{
p++;//先跳過'('
u.left = solve(), p++;//返回左子樹編號,並跳過','
u.right = solve(),p++;//返回右子樹編號,並跳過')'
}
if (dict.count(u))
{
cnt--;//子樹出現過,個數減少1
return dict[u];//返回這顆子樹的編號
}
return dict[u] = id;//如果這棵樹是首次出現,給它編號
}
void print(int v)
{
if(done[v] == rnd) printf("%d", v + 1);//已經輸出過了,輸出序號即可
else
{
done[v] = rnd;//不需要對done數組初始化,只需要用這一輪特有的rnd標記即可
printf("%s", node[v].s.c_str());//輸出樹根的字母
if (node[v].left != -1)//含有左右子樹
{
putchar('(');
print(node[v].left);//遞歸輸出左右子樹
putchar(',');
print(node[v].right);
putchar(')');
}
}
}
int main()
{
//freopen("test.txt", "r", stdin);
scanf("%d", &T);
for (rnd = 1; rnd <= T;rnd++)
{
dict.clear();
cnt = 0;
scanf("%s", expr);
p = expr;//用指針p掃描expr
print(solve());
putchar(10);//打印換行符
}
return 0;
}
1662 - Brackets Removal
題目鏈接:1662 - Brackets Removal
參考博文:Brackets Removal UVA - 1662
1395 - Slim Span
題目鏈接:1395 - Slim Span
題目大意:給出一個n結點圖,求最大邊減最小邊儘量小的生成樹。
思路:我使用了二分+kruskal算法。先二分遍歷最大邊減最小邊的值,然後求滿足該條件下是否存在生成樹,如果存在可以減小該值,如果不存在則增加該值。
代碼:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
struct Node
{
int a, b, w;
Node(int a=-1, int b=-1, int w=-1):a(a), b(b), w(w){}
bool operator < (Node &A) const
{
if(w==A.w)
return a<A.a;
else
return w<A.w;
}
};
Node E[10000];
vector<int> v;
int father[105], Rank[105];
int n, m;
//並查集操作
void Init()
{
for(int i=0; i<105; i++)
{
father[i] = i;
Rank[i] = 1;
}
}
int findRoot(int x)
{
int r = x;
while(r!=father[r]) r = father[r];
int j = x, i;
while(j!=father[j])
{
i = father[j];
father[j] = r;
j = i;
}
return r;
}
void join(int x, int y)
{
int fx = findRoot(x), fy = findRoot(y);
if(fx!=fy)
{
if(Rank[x]>Rank[y]) father[fy] = fx;
else
{
father[fx] = fy;
if(Rank[x]==Rank[y]) Rank[fy]++;
}
}
}
bool same(int x, int y)
{
return findRoot(x)==findRoot(y);
}
//判斷在最大值減最小值不超過mid的情況下,是否存在生成樹(kruskal算法)
bool solve(int mid)
{
for(int i=0; i<=m-(n-1); i++)//i表示起始的邊
{
Init();
v.clear();
for(int j=i; j<m; j++)//遍歷
{
if(E[j].w-E[i].w>mid) break;//超過條件,退出
if(!same(E[j].a, E[j].b))
{
join(E[j].a, E[j].b);
v.push_back(E[j].w);
}
}
if(v.size()==n-1) return 1;//存在生成樹
}
return 0;//不存在生成樹
}
int main()
{
while(cin >> n >> m && (n+m))
{
for(int i=0; i<m; i++)
cin >> E[i].a >> E[i].b >> E[i].w;
sort(E, E+m);
//二分枚舉
int l = 0, r = E[m-1].w-E[0].w+1, mid;
for(int i=0; i<100; i++)
{
mid = (l+r)/2;
if(solve(mid)) r = mid;//滿足條件,則減小mid
else l = mid;
}
if(r!=E[m-1].w-E[0].w+1)
printf("%d\n", r);
else
printf("-1\n");
}
return 0;
}
1151 - Buy or Build
題目鏈接:1151 - Buy or Build
- 題目大意:給定n個點,你的任務是讓它們都連通。你可以新建一些邊,費用等於兩點距離的平方(當然越小越好),另外還有幾種“套餐”,可以購買,你購買的話,那麼有些邊就可以連接起來,每個“套餐”,也是要花費的,讓你求出最少花費。
- 思路:先在不加套餐的情況下存儲MST所有的邊,然後枚舉套餐,將套餐內的點集合爲一個並查集,然後在原MST的邊中選邊彌補套餐的不足(將所有點連通起來)。
代碼:
#include <bits/stdc++.h>
using namespace std;
vector<int> pl[10];
int cost[10],x[1005],y[1005],pre[1005];
int medge[1005];
struct node
{
int u,v,dis;
bool operator < (const struct node a) const
{
return dis < a.dis;
}
}edge[1005 * 1005];
int dist(int i,int j)
{
return (x[i] - x[j]) * (x[i] - x[j]) + (y[i] - y[j]) * (y[i] - y[j]);
}
int findset(int x)
{
if(x == pre[x]) return x;
else return pre[x] = findset(pre[x]);
}
inline bool join(int x,int y)
{
int fx,fy;
fx = findset(x),fy = findset(y);
if(fx != fy) {
pre[fy] = fx;
return true;
}
return false;
}
void init(int n)
{
for(int i = 1; i <= n; ++i) {
pre[i] = i;
}
}
int main(void)
{
int T,n,num,cnt,temp,edge_cnt,ans,anss,flag = 0;
scanf("%d",&T);
while(T--) {
scanf("%d %d",&n,&num);
//套餐
for(int i = 0; i < num; ++i) {
scanf("%d %d",&cnt,&cost[i]);
pl[i].clear();
while(cnt--) {
scanf("%d",&temp);
pl[i].push_back(temp);
}
}
//座標
for(int i = 1; i <= n; ++i) {
scanf("%d %d",&x[i],&y[i]);
}
edge_cnt = 0;
for(int i = 1; i <= n; ++i) {
for(int j = i + 1; j <= n; ++j) {
edge[edge_cnt].u = i;
edge[edge_cnt].v = j;
edge[edge_cnt].dis = dist(i,j);
edge_cnt++;
}
}
sort(edge,edge + edge_cnt);
cnt = 0;
ans = 0;
init(n);
//不加套餐的MST
for(int i = 0; i < edge_cnt; ++i) {
if(join(edge[i].u,edge[i].v)) {
medge[cnt] = i;
ans += edge[i].dis;
cnt++;
}
if(cnt == n - 1) break;
}
//枚舉套餐
for(int i = 0;i < (1 << num); ++i) {
init(n);
anss = 0,cnt = 0;
for(int j = 0; j < num; ++j) {
if(i & (1 << j)) {//如果使用該套餐
temp = pl[j][0];
anss += cost[j];
//將套餐內的點集合爲一個並查集
for(int k = 1; k < pl[j].size(); k++) {
if(join(pl[j][k],temp)) cnt++;
}
}
}
//在原始的MST邊中取出一些邊來彌補選擇套餐後不連通的點
for(int j = 0; j <= n - 1; ++j) {
if(join(edge[medge[j]].u,edge[medge[j]].v)) {
anss += edge[medge[j]].dis;
cnt++;
}
if(cnt == n - 1) break;
}
ans = min(ans,anss);
}
if(flag) {
printf("\n");
}
flag = 1;
printf("%d\n",ans);
}
return 0;
}
247 - Calling Circles
- 題目大意:輸出所有電話圈(電話圈:要求兩個人直接或間接互相接通)。
- 思路:使用floyd判斷通路。先使用鄰接矩陣保存邊,然後使用floyd求出每兩個點之間是否有通路。當且僅當兩個點之間互相可達,才表示二者在一個圈內。
代碼:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <map>
#include <set>
using namespace std;
const int INF = 1<<29;
int m, n;
int flag[30];
string A, B;
int G[30][30];
map<int, string> M;
map<string, int> ID;
set<string> Set;
int d[30][30];
void Init()
{
M.clear();
ID.clear();
Set.clear();
memset(G, 0, sizeof(G));
}
//將字符串轉換爲數字
int setID(string a)
{
if(ID.count(a)) return ID[a];
int t = Set.size();
ID[a] = t;
M[t] = a;
Set.insert(a);
return ID[a];
}
void floyd()
{
for(int k=0; k<m; k++)
{
for(int i=0; i<m; i++)
{
for(int j=0; j<m; j++)
{
G[i][j] = G[i][j] || (G[i][k]&&G[k][j]);//維護更新通路矩陣
}
}
}
}
int main()
{
int kase = 1;
while(cin >> m >> n && (m+n))
{
Init();
for(int i=0; i<n; i++)
{
cin >> A >> B;
int a = setID(A), b = setID(B);
G[a][b] = 1;//a可以到達b
}
floyd();
if(kase>1) cout << endl;
cout << "Calling circles for data set " << kase++ << ":" << endl;
//按格式輸出
memset(flag, 0, sizeof(flag));
for(int i=0; i<m; i++)
{
if(flag[i]) continue;
cout << M[i];
flag[i] = 1;
for(int j=0; j<m; j++)
{
if(i==j) continue;
if(flag[j]) continue;
if(G[i][j]==1 && G[j][i]==1)
{
flag[j] = 1;
cout << ", " << M[j];
}
}
cout << endl;
}
}
return 0;
}
10048 - Audiophobia
題目鏈接:10048 - Audiophobia
- 題目大意:給出一個c個點,s條邊組成的無向圖,求一點到另一點的路徑上最大權值最小的路徑,輸出這個值。
- 思路:二分法+bfs。先將所有邊存儲排序,然後二分枚舉邊,使用bfs判斷是否存儲每一邊均不大於該值的情況下是否存在一條路徑,如果存在則將該值變小,否則將該值變大。
代碼:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
#include <algorithm>
using namespace std;
const int INF = 1<<29;
int C, S, Q;
int a, b, w, c1, c2;
vector<int> v;//存儲所有邊的權值
int G[105][105];
int vis[105];
void Init()
{
v.clear();
}
//使用bfs來判斷是否存在一條路徑,路徑上的各個邊的權值均不大於v[mid]
bool solve(int mid)
{
memset(vis, 0, sizeof(vis));
queue<int> q;
q.push(c1);
vis[c1] = 1;
while(!q.empty())
{
int u = q.front(); q.pop();
if(u==c2) return 1;
for(int i=1; i<=C; i++)
{
if(!vis[i] && G[u][i]<=v[mid])
{
vis[i] = 1;
q.push(i);
}
}
}
return 0;
}
int main()
{
#ifdef ONLINE_JUDGE
#else
freopen("input.txt","r",stdin);
freopen("output.txt","w",stdout);
#endif
int kase = 1;
while(scanf("%d%d%d", &C, &S, &Q)!=EOF && (C+S+Q))
{
//初始化
Init();
for(int i=0; i<105; i++)
{
for(int j=0; j<105; j++)
G[i][j] = INF;
}
for(int i=0; i<S; i++)
{
scanf("%d%d%d", &a, &b, &w);
G[a][b] = w, G[b][a] = w;
v.push_back(w);
}
sort(v.begin(), v.end());
if(kase>1) printf("\n");
printf("Case #%d\n", kase++);
for(int i=1; i<=Q; i++)
{
scanf("%d%d", &c1, &c2);
//二分法求值
int l = 0, r = v.size();
int cnt = (l+r)/2, mid = (l+r)/2;
for(int i=0; i<100; i++)
{
if(solve(mid)) r = mid;
else l = mid;
mid = (l+r)/2;
if(mid==cnt) break;
cnt = mid;
}
if(r==v.size())//不存在路徑
printf("no path\n");
else
printf("%d\n", v[r]);
}
}
return 0;
}
658 - It’s not a Bug, it’s a Feature!
題目鏈接:658 - It’s not a Bug, it’s a Feature!
參考博文:洛谷 題解 UVA658 【這不是bug,而是特性 It’s not a Bug, it’s a Feature!】
- 題目大意:補丁在修正BUG時,有時也會引入新的BUG,假定有n(n<=20)個潛在BUG,和m(m<=100)個補丁,每個補丁用兩個長度爲n的字符串表示,其中字符串的每個位置表示一個BUG,第一個串表示打補丁之前的狀態 (“-”表示該BUG必須不存在,“+”表示該補丁必須存在,0表示無所謂),第二串表示打補丁之後的狀態 ("-“表示不存在,”+"表示存在,"0"表示不變)。每個補丁有一定的執行時間,你的任務是用最小的時間把所有BUG都存在的軟件變得沒有BUG。
代碼:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=20+10,MAXM=100+10;
int n,m;
struct Node
{
int t;
int a[MAXN];//狀態由a轉換爲b
int b[MAXN];
}patch[MAXM];
int d[2000000];//保存路徑長度
int T;
void init(int k)
{
cin>>patch[k].t;//該補丁的花費
string s;
cin>>s;//打補丁之前的狀態
for(int i=0;i<s.size();i++)
{
if(s[i]=='-')patch[k].a[i+1]=-1;
else if(s[i]=='0')patch[k].a[i+1]=0;
else patch[k].a[i+1]=1;
}
cin>>s;//打補丁之後的狀態
for(int i=0;i<s.size();i++)
{
if(s[i]=='-')patch[k].b[i+1]=-1;
else if(s[i]=='0')patch[k].b[i+1]=0;
else patch[k].b[i+1]=1;
}
}
//查看該當前狀態是否可以使用第k個補丁
bool check(int sum,int k)//sum表示當前狀態,k表示第k個補丁
{
for(int i=1;i<=n;i++)//比對當前狀態是否與補丁的應用狀態相同
{
if(patch[k].a[i]==0)continue;
if(patch[k].a[i]==-1 && (sum>>(n-i)&1)==0)continue;
if(patch[k].a[i]==1 && (sum>>(n-i)&1)==1)continue;
return 0;
}
return 1;
}
//獲取使用第k個補丁轉化後的狀態
int get(int sum,int k)//sum表示當前狀態,k表示第k個補丁
{
for(int i=1;i<=n;i++)//使用補丁轉化當前狀態sum
{
if(patch[k].b[i]==0)continue;//該位不需要轉化
if(patch[k].b[i]==-1)//將對應位變成0
{
int t = (1<<n)-1-(1<<(n-i));
sum = sum&t;
}
else//將對應位變成1
{
int t = 1<<(n-i);
sum = sum|t;
}
}
return sum;
}
void SPFA()
{
memset(d,0x3f,sizeof(d));
queue<int>q;
q.push((1<<n)-1);//初始全部均爲bug,每一位均爲1
d[(1<<n)-1]=0;
while(q.size())
{
int now=q.front();
q.pop();
//枚舉m個補丁
for(int i=1;i<=m;i++)
{
if(!check(now,i))continue;
int x=get(now,i);//獲取使用第i個補丁轉化後的狀態
if(d[now]+patch[i].t<d[x])
{
d[x]=d[now]+patch[i].t;
q.push(x);
}
}
}
}
int main()
{
while(cin>>n>>m)
{
if(n==0&&m==0)break;
T++;
for(int i=1;i<=m;i++)
init(i);
SPFA();
printf("Product %d\n",T);
if(d[0]==0x3f3f3f3f)
printf("Bugs cannot be fixed.\n");
else
printf("Fastest sequence takes %d seconds.\n",d[0]);
cout<<endl;
}
return 0;
}
12661 - Funny Car Racing
- 題目大意:給出一個有向圖,每條邊都有三個值,打開時間間隔,關閉時間間隔,通過它需要多少時間,路的開關是循環往復的。給你起點和終點,問你最少需要多少時間纔可以從起點到達終點(保證一定存在這種路)。
- 思路:dijstra的變體。由於路有開關,所以需要在計算從一個點到達另一個點的時間,主要是更新距離d時,需要分條件判斷(是否能夠直接通過或者等待)。
代碼:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
const int MAXM = 50001;
const int MAXN = 500;
const int INF = 1<<29;
int d[MAXN], vis[MAXN];
int n, m, s, t;
int u, v, a, b, t1;
struct Node
{
int v, a, b, t;
Node(int v=-1, int a=-1, int b=-1, int t=-1):v(v), a(a), b(b), t(t){}
};
vector<Node> G[MAXM];
void dijstra(int s)
{
memset(vis, 0, sizeof(vis));
fill(d, d+MAXN, INF);
d[s] = 0;
while(1)
{
int minv = INF, u = 0;
for(int i=1; i<=n; i++)
{
if(!vis[i] && minv>d[i])
{
minv = d[i];
u = i;
}
}
if(minv==INF)
{
cout << d[t] << endl;
return;
}
vis[u] = 1;
for(int i=0; i<G[u].size(); i++)
{
Node v = G[u][i];
if(vis[v.v]) continue;
int r = d[u]%(v.a+v.b);
if(r+v.t<=v.a)//可以在關閉前通過
{
if(d[v.v]>d[u]+v.t)
d[v.v] = d[u]+v.t;
}
else if(v.t<=v.a)//在關閉後重新打開時通過
{
if(d[v.v]>d[u]+v.t+v.a+v.b-r)
d[v.v] = d[u]+v.t+v.a+v.b-r;
}
}
}
}
int main()
{
#ifdef ONLINE_JUDGE
#else
freopen("input.txt","r",stdin);
freopen("output.txt","w",stdout);
#endif
int kase = 1;
while(cin >> n >> m >> s >> t)
{
for(int i=0; i<m; i++)
{
cin >> u >> v >> a >> b >> t1;
G[u].push_back(Node(v, a, b, t1));
}
cout << "Case " << kase++ << ": ";
dijstra(s);
for(int i=1; i<=n; i++) G[i].clear();
}
return 0;
}
821 - Page Hopping
題目鏈接:821 - Page Hopping
- 題目大意:給出有向圖,給定的節點之間長度爲1,求出平均長度,既所有道路長度和/道路數量。
- 思路:簡單floyd題目。直接使用floyd統計任意兩個點之間的距離,然後求其平均即可。
代碼:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <set>
using namespace std;
const int MAX = 105;
const int INF = 1<<29;
int G[MAX][MAX], N;
void Init()
{
for(int i=0; i<MAX; i++)
{
for(int j=0; j<MAX; j++)
{
G[i][j] = INF;
}
}
}
void floyd()
{
for(int k=1; k<=N; k++)
{
for(int i=1; i<=N; i++)
{
if(G[i][k]==INF) continue;
for(int j=1; j<=N; j++)
{
if(G[k][j]==INF) continue;
G[i][j] = min(G[i][j], G[i][k]+G[k][j]);
}
}
}
}
int main()
{
int a, b, kase = 1;
while(cin >> a >> b && (a+b))
{
Init();
N = 0;
G[a][b] = 1;
N = max(a, b);
while(cin >> a >> b && (a+b))
{
G[a][b] = 1;
N = max(N, a);
N = max(N, b);
}
floyd();
int sum = 0, cnt = 0;
for(int i=1; i<=N; i++)
{
for(int j=1; j<=N; j++)
{
if(i==j) continue;
if(G[i][j]!=INF)
{
sum += G[i][j];
cnt++;
}
}
}
printf("Case %d: average length between pages = %.3f clicks\n", kase++, sum/(cnt*1.0));
}
return 0;
}
10801 - Lift Hopping
題目鏈接:10801 - Lift Hopping
- 題目大意:有一棟100層的大樓(標號爲0~99),裏面有n個電梯(不超過5個),以及要到達的層數(aid),然後是每個電梯走一層所需的時間,再n行就是對應每個電梯可以到達的層數,數量不定。然後每裝換一次電梯需要等待60秒,問,最快能多快到達目標層數。
- 思路:最短路問題。這道題的難點在於將問題轉換爲最短路問題,並且需要自己建圖。通過將一個電梯可以到達的樓層看做點,通過電梯在兩個可以停靠的點之間的用時看做邊的權值(取權值最小即可)。而在換電梯時需要考慮將換電梯的時間,這個在求最短路的過程中可以考慮。以下是我使用floyd算法的解法。
代碼:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <sstream>
#include <cmath>
#include <set>
using namespace std;
const int INF = 1<<29;
int T[10], n, k, m;
int f[10][101], G[101][101];
string s;
set<int> S;//儲存所有點
void Init()
{
S.clear();
for(int i=0; i<101; i++)
{
for(int j=0; j<101; j++)
{
G[i][j] = INF;
}
}
}
void floyd()
{
set<int>::iterator k, i, j;
for(k=S.begin(); k!=S.end(); k++)
{
for(i=S.begin(); i!=S.end(); i++)
{
if(G[*i][*k]==INF || *i==*k) continue;
for(j=S.begin(); j!=S.end(); j++)
{
if(G[*k][*j]==INF || *k==*j) continue;
G[*i][*j] = min(G[*i][*j], G[*i][*k]+G[*k][*j]+60);//通過k來換電梯
}
}
}
}
int main()
{
#ifdef ONLINE_JUDGE
#else
freopen("input.txt","r",stdin);
freopen("output.txt","w",stdout);
#endif
int x;
while(cin >> n >> m)
{
Init();
for(int i=0; i<n; i++)
cin >> T[i];
getchar();
for(int i=0; i<n; i++)
{
getline(cin, s);
stringstream ss(s);
int t = 0;
while(ss >> x)
{
f[i][t++] = x;
S.insert(x);
}
//建立圖
for(int j=0; j<t; j++)
{
int a = f[i][j];
for(int k=0; k<t; k++)
{
int b = f[i][k];
G[a][b] = min(G[a][b], T[i]*abs(a-b));//注意使用min
G[b][a] = G[a][b];
}
}
}
floyd();
if(G[0][m]==INF) printf("IMPOSSIBLE\n");
else printf("%d\n", G[0][m]);
}
return 0;
}
11671 - Sign of Matrix
題目鏈接:11671 - Sign of Matrix
參考博文:Uva11671_Sign of Matrix——差分約束
表達式總結
參考博文:表達式總結