10/15
这几天先专心刷一下图论的基础题目,也蛮多的,慢慢来。。。
例题11-1 uva 12219
题意:给你一个表达式,然后又一些子树在之前重复出现过,先要你给这些子树出现的顺序编个号1.。。N,然后如果重复出现就用编号替代,输出替代之后的表达式。
题解:这是一个表达式树的问题,显示建树,如果让我来写的话,很麻烦,搞不好复杂度是O(n^2),因为字符串是一直扫描下去的,所以就利用一个指针作为全局,然后一直扫下去,就忽略一个左括号,建左树,然后忽略逗号,建右树,忽略右括号,然后一直扫下去,就可以在O(n)时间内把整棵树给建好。接下来就是怎么记录一颗子树,紫书上利用的是一个结构体(s,l,r)s代表这个串,其实如果比较串的话会很费时间,lrj做了一个处理,就是hash成一个值来存,l和r放的是这个树的编号,然后在map里面进行映射。
这里我学到了不少技巧:hash减少复杂度,定义map的比较函数,利用建树的特点进行优化。。。。详见代码。
1 /*zhen hao*/ 2 #include <bits/stdc++.h> 3 using namespace std; 4 5 const int maxn = 8e4 + 100; 6 char str[maxn * 5], *p; 7 int tcase, cnt, vis[maxn]; 8 9 struct Node { 10 string s; 11 int hash, left, right; 12 bool operator < (const Node& a) const { 13 if (hash != a.hash) return hash < a.hash; 14 if (left != a.left) return left < a.left; 15 return right < a.right; 16 } 17 }; 18 19 map<Node,int> mp; 20 Node nodes[maxn]; 21 22 int build_tree() { 23 int id = ++cnt; 24 Node& temp = nodes[id]; 25 temp = (Node){"", 0, -1, -1}; 26 while (isalpha(*p)) { 27 temp.hash = temp.hash * 27 + *p - ‘0‘ + 1; 28 temp.s.push_back(*p); p++; 29 } 30 if ((*p) == ‘(‘) { 31 p++; 32 temp.left = build_tree(); 33 p++; 34 temp.right = build_tree(); 35 p++; 36 } 37 if (mp.count(temp)) { 38 --cnt; 39 return mp[temp]; 40 } 41 return mp[temp] = id; 42 } 43 44 void print(int u) { 45 if (vis[u] == tcase) { 46 printf("%d", mp[nodes[u]]); 47 return; 48 } 49 cout << nodes[u].s; 50 if (nodes[u].left != -1) { 51 printf("("); 52 print(nodes[u].left); 53 printf(","); 54 print(nodes[u].right); 55 printf(")"); 56 } 57 vis[u] = tcase; 58 } 59 60 int main() { 61 //freopen("case.in", "r", stdin); 62 int T; 63 scanf("%d", &T); 64 for (tcase = 1; tcase <= T; tcase++) { 65 cnt = 0; 66 mp.clear(); 67 scanf("%s", str); 68 p = str; 69 build_tree(); 70 print(1); 71 puts(""); 72 } 73 return 0; 74 }
代码君
例题11-2 uva1395
题意:给你一个图,让你选一个生成树使得生成树中的最大边和最小边的差值最小。
题解:由于边数很小,所以暴力找即可。
1 /*zhen hao*/ 2 #include <bits/stdc++.h> 3 using namespace std; 4 5 const int maxn = 8e4 + 100; 6 char str[maxn], *p; 7 int tcase, cnt, vis[maxn]; 8 9 struct Node { 10 string s; 11 int hash, left, right; 12 bool operator < (const Node& a) const { 13 if (hash != a.hash) return hash < a.hash; 14 if (left != a.left) return left < a.left; 15 return right < a.right; 16 } 17 }; 18 19 map<Node,int> mp; 20 Node nodes[maxn]; 21 22 int build_tree() { 23 int id = ++cnt; 24 Node& temp = nodes[id]; 25 temp = (Node){"", 0, -1, -1}; 26 while (isalpha(*p)) { 27 temp.hash = temp.hash * 27 + *p - ‘0‘ + 1; 28 temp.s.push_back(*p); p++; 29 } 30 if ((*p) == ‘(‘) { 31 p++; 32 temp.left = build_tree(); 33 p++; 34 temp.right = build_tree(); 35 p++; 36 } 37 if (mp.count(temp)) { 38 --cnt; 39 return mp[temp]; 40 } 41 return mp[temp] = id; 42 } 43 44 void print(int u) { 45 if (vis[u] == tcase) { 46 printf("%d", mp[nodes[u]]); 47 return; 48 } 49 cout << nodes[u].s; 50 if (nodes[u].left != -1) { 51 printf("("); 52 print(nodes[u].left); 53 printf(","); 54 print(nodes[u].right); 55 printf(")"); 56 } 57 vis[u] = tcase; 58 } 59 60 int main() { 61 //freopen("case.in", "r", stdin); 62 int T; 63 scanf("%d", &T); 64 for (tcase = 1; tcase <= T; tcase++) { 65 cnt = 0; 66 mp.clear(); 67 scanf("%s", str); 68 p = str; 69 build_tree(); 70 print(1); 71 puts(""); 72 } 73 return 0; 74 }
代码君
例题11-3 uva 1151
题意:给你n个点,点与点的权值是他们的欧几里得距离的平方,然后给你q个套餐,花费为C[i],购买连接的点,也就是花费C[i]购买一些点使得这些点的权值为0,问你最小花费得到所有的点。
题解:这里暴力枚举q个套餐,因为q只有8,所以只是1<<q,然后是边,我们只要考虑最小生成树中的边,因为当在套餐中,不在生成树中的边权值为0,然后会去掉的一定是生成树中的某一条边(画图易知),这样的复杂度就是(1<<q)n;我们只需要小球一次最小生成树,然后记录这里的所有边,然后就枚举套餐,把套餐的所有点都加进并查集,然后在把生成树中的点加进并查集,去一个最小值就是答案。
1 /*zhen hao*/ 2 #include <bits/stdc++.h> 3 using namespace std; 4 5 const int maxm = 1e6 + 10; 6 const int maxn = 1e3 + 10; 7 const int maxq = 10; 8 int n, q; 9 int wi[maxq], fa[maxn], x[maxn], y[maxn]; 10 vector<int> tc[maxq]; 11 12 struct Edge { 13 int u, v, w; 14 bool operator < (const Edge& a) const { 15 return w < a.w; 16 } 17 }; 18 19 Edge before[maxm]; 20 Edge after[maxm]; 21 22 int find_root(int rt) { 23 if (rt == fa[rt]) return rt; 24 return fa[rt] = find_root(fa[rt]); 25 } 26 27 int find_MST(int x, int need, bool flag = false) { 28 if (need == 1) return 0; 29 int ans = 0, cnt = 0; 30 for (int i = 0; i < x; i++) { 31 int fu = find_root(before[i].u); 32 int fv = find_root(before[i].v); 33 if (fu != fv) { 34 fa[fu] = fv; 35 ans += before[i].w; 36 if (flag) after[cnt++] = before[i]; 37 if (--need == 1) break; 38 } 39 } 40 return ans; 41 } 42 43 int main() { 44 //freopen("case.in", "r", stdin); 45 int T; 46 scanf("%d", &T); 47 while (T--) { 48 scanf("%d%d", &n, &q); 49 for (int i = 0; i < q; i++) { 50 int x, t; 51 scanf("%d%d", &x, wi + i); 52 tc[i].clear(); 53 while (x--) { 54 scanf("%d", &t); 55 tc[i].push_back(t); 56 } 57 } 58 for (int i = 1; i <= n; i++) scanf("%d%d", &x[i], &y[i]); 59 int cnt = 0; 60 for (int i = 1; i <= n; i++) { 61 for (int j = i + 1; j <= n; j++) { 62 before[cnt++] = (Edge){i, j, (x[i] - x[j]) * (x[i] - x[j]) + (y[i] - y[j]) * (y[i] - y[j])}; 63 } 64 } 65 66 sort(before, before + cnt); 67 for (int i = 1; i <= n; i++) fa[i] = i; 68 int ans = find_MST(cnt, n, true); 69 memcpy(before, after, sizeof(Edge) * (n - 1)); 70 71 for (int i = 1; i < (1 << q); i++) { 72 int now = n, sum = 0; 73 for (int j = 1; j <= n; j++) fa[j] = j; 74 for (int j = 0; j < q; j++) if (i & (1 << j)) { 75 sum += wi[j]; 76 int sz = tc[j].size(); 77 for (int k = 1; k < sz; k++) { 78 int u = find_root(tc[j][k]), v = find_root(tc[j][0]); 79 if (u != v) { 80 now--; fa[u] = v; 81 } 82 } 83 } 84 ans = min(ans, find_MST(n - 1, now) + sum); 85 } 86 printf("%d\n", ans); 87 if (T) puts(""); 88 } 89 return 0; 90 }
代码君
例题11-4 uva247
题意:给你n个人,m条电话信息,表示u可以打电话给v,然后问你可以互相打电话的放在一个集合,让你输出所有集合。
题解:经典题目,floyd找传递闭包,我们把floyd的递推式子改成G[i][j] = G[i][j] || G[i][k] && G[k][j]即可。具体原理,理解了floyd算法就很容易理解了。。。
1 /*zhen hao*/ 2 #include <bits/stdc++.h> 3 using namespace std; 4 5 const int maxn = 30; 6 int G[maxn][maxn], vis[maxn]; 7 string str[maxn]; 8 map<string,int> mp; 9 10 int main() { 11 //freopen("case.in", "r", stdin); 12 int n, m, tcase = 0; 13 while (scanf("%d%d", &n, &m) == 2 && (n + m)) { 14 if (tcase) puts(""); 15 mp.clear(); 16 memset(G, 0, sizeof(G)); 17 string s1, s2; 18 int id = 0, id1, id2; 19 for (int i = 0; i < m; i++) { 20 cin >> s1 >> s2; 21 if (!mp.count(s1)) id1 = mp[s1] = ++id; 22 else id1 = mp[s1]; 23 if (!mp.count(s2)) id2 = mp[s2] = ++id; 24 else id2 = mp[s2]; 25 G[id1][id2] = 1; 26 str[id1] = s1; 27 str[id2] = s2; 28 } 29 for (int k = 1; k <= n; k++) { 30 for (int i = 1; i <= n; i++) { 31 for (int j = 1; j <= n; j++) { 32 G[i][j] = G[i][j] || (G[i][k] && G[k][j]); 33 } 34 } 35 } 36 memset(vis, 0, sizeof vis); 37 printf("Calling circles for data set %d:\n", ++tcase); 38 for (int i = 1; i <= n; i++) { 39 if (vis[i]) continue; 40 cout << str[i]; 41 for (int j = i + 1; j <= n; j++) { 42 if (G[i][j] && G[j][i]) { 43 cout << ", " << str[j]; 44 vis[j] = 1; 45 } 46 } 47 puts(""); 48 } 49 } 50 return 0; 51 }
代码君
例题11-5 uva 10048
题意:给你一幅图,n个点,m条无向边的带权图,然后又q个询问,u和v,问你u到v的所有路径中最小的路径中最大权值是多少?
题解:这题也是floyd算法的经典应用,只要floyd的原理是枚举可能的中间节点,同样,可能是G[i][k]或者是G[k][j]最长,所以只要max{ G[i][k], G[k][j] },然后取最小值即可,注意要i到k且k到j都有可行路径的时候才可以转移。
1 /*zhen hao*/ 2 #include <bits/stdc++.h> 3 using namespace std; 4 5 const int maxn = 110; 6 const int INF = 1e9; 7 int G[maxn][maxn]; 8 9 int main() { 10 // freopen("case.in", "r", stdin); 11 int n, m, q, tcase = 0; 12 while (scanf("%d%d%d", &n, &m, &q) == 3 && (n + m + q)) { 13 if (tcase) puts(""); 14 for (int i = 1; i <= n; i++) 15 for (int j = 1; j <= n; j++) 16 G[i][j] = INF; 17 for (int i = 0; i < m; i++) { 18 int u, v, w; 19 scanf("%d%d%d", &u, &v, &w); 20 G[u][v] = G[v][u] = w; 21 } 22 for (int k = 1; k <= n; k++) { 23 for (int i = 1; i <= n; i++) { 24 for (int j = 1; j <= n; j++) { 25 if (G[i][k] != INF && G[k][j] != INF) 26 G[i][j] = min(G[i][j], max(G[i][k], G[k][j])); 27 } 28 } 29 } 30 printf("Case #%d\n", ++tcase); 31 while (q--) { 32 int u, v; 33 scanf("%d%d", &u, &v); 34 if (G[u][v] != INF) 35 printf("%d\n", G[u][v]); 36 else puts("no path"); 37 } 38 } 39 return 0; 40 }
代码君
例题11-6 uva658
题意:有n种bug,m个补丁,然后补丁的描述是给你一个时间ti,两个字符串s和t,ti表示这个bug的执行时间,s表示打补丁之前的bug状态,+表示这个位置一定要有bug,-表示这个位置一定要没有bug,然后0表示这个位置可以有可以没有,t表示这个bug打完补丁之后的状态,+表示打完之后这里会有bug,-表示这个这个位置的bug会消失,0表示维持不变。现在给你一个都是bug的状态,问你存不存在方案变成没有bug的状态,有的话最短时间是多少?
题解:首先是怎样枚举状态,我们用1表示这个位置有bug,0表示这个位置没有bug,然后就是1<<n-1到0的最短路,如果直接把图建出来的话,节点数太多了,有很多没有用的节点,所以我们就边搜索边枚举,也就是当要扩展一个节点的时候就遍历每一个补丁,看有哪些状态会生成,然后在进行找最短路。可扩展的状态少,虽然节点数多,所以勉强可以跑出结果。
/*zhen hao*/ #include <bits/stdc++.h> using namespace std; const int maxm = 1e2 + 10; const int INF = 1e9; const int maxn = 1 << 20; int n, m; int d[maxn], done[maxn], digit[maxm]; struct Node { int ti; string s, t; }; Node nodes[maxn]; struct HeapNode { int d, u; bool operator < (const HeapNode& rhs) const { return d > rhs.d; } }; int get_v(int u, int& v, int id) { for (int i = n - 1; i >= 0; i--) { digit[i] = u % 2; u /= 2; } v = 0; for (int i = 0; i < n; i++) { if (nodes[id].s[i] == ‘+‘ && digit[i] != 1) return false; if (nodes[id].s[i] == ‘-‘ && digit[i] != 0) return false; if (nodes[id].t[i] == ‘+‘) digit[i] = 1; if (nodes[id].t[i] == ‘-‘) digit[i] = 0; v = v * 2 + digit[i]; } return true; } void dijkstra(int s) { priority_queue<HeapNode> Q; for (int i = 0; i <= s; i++) d[i] = INF; d[s] = 0; memset(done, false, sizeof done); Q.push((HeapNode){0, s}); while (!Q.empty()) { HeapNode x = Q.top(); Q.pop(); int u = x.u; if (done[u]) continue; done[u] = true; for (int i = 0; i < m; i++) { int v; if (!get_v(u, v, i)) continue; if (d[v] > d[u] + nodes[i].ti) { d[v] = d[u] + nodes[i].ti; Q.push((HeapNode){d[v], v}); } } } } int main() { //freopen("case.in", "r", stdin); int tcase = 0; while (scanf("%d%d", &n, &m) == 2 && (n + m)) { for (int i = 0; i < m; i++) { cin >> nodes[i].ti >> nodes[i].s >> nodes[i].t; } printf("Product %d\n", ++tcase); dijkstra((1 << n) - 1); if (d[0] != INF) printf("Fastest sequence takes %d seconds.\n\n", d[0]); else puts("Bugs cannot be fixed.\n"); } return 0; }
代码君(Me)
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 const int maxn = 20; 5 const int maxm = 110; 6 const int inf = 1e9; 7 int n, m, ti[maxm], done[1<<maxn], dist[1<<maxn]; 8 char before[maxm][maxn + 5], after[maxm][maxn + 5]; 9 10 struct Node { 11 int u, d; 12 bool operator < (const Node& a) const { 13 return d > a.d; 14 } 15 }; 16 17 int dijkstra(int s) { 18 for (int i = 0; i <= s; i++) dist[i] = inf, done[i] = 0; 19 priority_queue<Node> q; 20 q.push((Node){s, 0}); 21 dist[s] = 0; 22 while (!q.empty()) { 23 Node x = q.top(); q.pop(); 24 int u = x.u; 25 if (done[u]) continue; 26 if (u == 0) return x.d; 27 done[u] = 1; 28 for (int i = 0; i < m; i++) { 29 bool ok = true; 30 for (int j = 0; j < n && ok; j++) { 31 if (before[i][j] == ‘+‘ && !(u & (1 << j))) ok = false; 32 if (before[i][j] == ‘-‘ && u & (1 << j)) ok = false; 33 } 34 if (!ok) continue; 35 int v = u; 36 for (int j = 0; j < n; j++) { 37 if (after[i][j] == ‘+‘) v |= (1 << j); 38 if (after[i][j] == ‘-‘) v &= ~(1 << j); 39 } 40 if (dist[v] > dist[u] + ti[i]) { 41 dist[v] = dist[u] + ti[i]; 42 q.push((Node){v, dist[v]}); 43 } 44 } 45 } 46 return -1; 47 } 48 49 int main() { 50 //freopen("case.in", "r", stdin); 51 int tcase = 0; 52 while (scanf("%d%d", &n, &m) == 2 && (n + m)) { 53 for (int i = 0; i < m; i++) scanf("%d%s%s", &ti[i], before[i], after[i]); 54 int ans = dijkstra((1<<n) - 1); 55 printf("Product %d\n", ++tcase); 56 if (ans < 0) puts("Bugs cannot be fixed.\n"); 57 else printf("Fastest sequence takes %d seconds.\n\n", ans); 58 } 59 return 0; 60 }
代码君(Lrj)
例题11-7 uva 753
题意:给你n个插座,m个设备,k个转换器,首先是m个设备有设备名字和插头类型组成,所以设备当然是插在插座上的,然后转换器就是插座->插头,因为设备的B插头可以查到B->C的转换器,然后变成C插头的,并且转换器可以用无限个,最后问你n个插座有多少个是没插任何设备的。
题解:有两种做法;
第一种是网络流的做法,首先是超级源点s连接着设备,表示可以转移到设备,实际上连的是插头,然后是插头连着转换器,如果插头a可以变成插头b,那么就是连一条边,流量为无穷,最后还对应的插头连着插座,流量为1,最后求一个最大流就代表有设备的插头的最大数,n-最大数就是答案。
1 /*zhen hao*/ 2 #include <bits/stdc++.h> 3 using namespace std; 4 5 const int maxn = 410; 6 const int INF = 1000; 7 int n, m, k, cnt, vis[maxn]; 8 string str1[maxn], str2[maxn], str3[maxn], str4[maxn], t; 9 map<string, int> mp; 10 11 struct Edge { 12 int from, to, cap, flow; 13 }; 14 15 struct EK { 16 int n, m; 17 vector<Edge> edges; 18 vector<int> G[maxn]; 19 int a[maxn]; 20 int p[maxn]; 21 22 void init(int n) { 23 edges.clear(); 24 for (int i = 0; i < n; i++) G[i].clear(); 25 } 26 27 void add_edge(int from, int to, int cap) { 28 edges.push_back((Edge){from, to, cap, 0}); 29 edges.push_back((Edge){to, from, 0, 0}); 30 m = edges.size(); 31 G[from].push_back(m - 2); 32 G[to].push_back(m - 1); 33 } 34 35 int max_flow(int s, int t) { 36 int flow = 0; 37 for (;;) { 38 memset(a, 0, sizeof a); 39 queue<int> Q; 40 Q.push(s); 41 a[s] = INF; 42 while (!Q.empty()) { 43 int x = Q.front(); Q.pop(); 44 for (int i = 0; i < (int)G[x].size(); i++) { 45 Edge& e = edges[G[x][i]]; 46 if (!a[e.to] && e.cap > e.flow) { 47 p[e.to] = G[x][i]; 48 a[e.to] = min(a[x], e.cap - e.flow); 49 Q.push(e.to); 50 } 51 } 52 if (a[t]) break; 53 } 54 if (!a[t]) break; 55 for (int u = t; u != s; u = edges[p[u]].from) { 56 edges[p[u]].flow += a[t]; 57 edges[p[u] ^ 1].flow -= a[t]; 58 } 59 flow += a[t]; 60 } 61 return flow; 62 } 63 }; 64 65 EK ek; 66 67 void init_graph() { 68 mp.clear(); 69 memset(vis, 0, sizeof vis); 70 ek.init(maxn); 71 scanf("%d", &n); 72 for (int i = 1; i <= n; i++) cin >> str1[i]; 73 scanf("%d", &m); 74 cnt = n; 75 for (int i = 1; i <= m; i++) { 76 cin >> t >> str2[i]; 77 if (!mp.count(str2[i])) mp[str2[i]] = ++cnt; 78 vis[mp[str2[i]]]++; 79 } 80 scanf("%d", &k); 81 for (int i = 1; i <= k; i++) { 82 cin >> str3[i] >> str4[i]; 83 if (!mp.count(str3[i])) mp[str3[i]] = ++cnt; 84 if (!mp.count(str4[i])) mp[str4[i]] = ++cnt; 85 } 86 ++cnt; 87 for (int i = 1; i <= n; i++) { 88 ek.add_edge(i, cnt, 1); 89 if (!mp.count(str1[i])) continue; 90 ek.add_edge(mp[str1[i]], i, 1); 91 } 92 for (int i = n + 1; i < cnt; i++) { 93 if (!vis[i]) continue; 94 ek.add_edge(0, i, vis[i]); 95 } 96 for (int i = 1; i <= k; i++) { 97 ek.add_edge(mp[str3[i]], mp[str4[i]], INF); 98 } 99 } 100 101 int main() { 102 //freopen("case.in", "r", stdin); 103 int T; 104 scanf("%d", &T); 105 while (T--) { 106 init_graph(); 107 cout << m - ek.max_flow(0, cnt) << endl; 108 if (T) puts(""); 109 } 110 return 0; 111 }
代码君
第二种是floyd+二分图,我们先用floyd求出什么插头是相互转化的,然后用一个矩阵,x表示设备,y代表插座,因为同一设备不能连在同一个插座,所以最后求个最大匹配就是有设备的最大数,然后n-最大数就是答案。还要注意一个地方就是设备是可以连到原来的插头的,但是不存在对应这种插头的插座是不能连的,所以要注意连边。。。
1 /*zhen hao*/ 2 #include <bits/stdc++.h> 3 using namespace std; 4 5 const int maxn = 510; 6 int n, m, k, cnt, used[maxn], from[maxn]; 7 int G1[maxn][maxn], G2[maxn][maxn]; 8 string str1[maxn], t1, t2; 9 map<string, int> mp; 10 11 void floyd() { 12 for (int k = 1; k <= cnt; k++) 13 for (int i = 1; i <= cnt; i++) 14 for (int j = 1; j <= cnt; j++) 15 G1[i][j] = G1[i][j] || (G1[i][k] && G1[k][j]); 16 } 17 18 void init_graph() { 19 memset(G1, 0, sizeof(G1)); 20 memset(G2, 0, sizeof(G2)); 21 mp.clear(); 22 scanf("%d", &n); 23 for (int i = 1; i <= n; i++) cin >> t1, mp[t1] = i; 24 cnt = n; 25 scanf("%d", &m); 26 for (int i = 1; i <= m; i++) { 27 cin >> t1 >> str1[i]; 28 if (!mp.count(str1[i])) mp[str1[i]] = ++cnt; 29 } 30 scanf("%d", &k); 31 for (int i = 1; i <= k; i++) { 32 cin >> t1 >> t2; 33 if (!mp.count(t1)) mp[t1] = ++cnt; 34 if (!mp.count(t2)) mp[t2] = ++cnt; 35 int x = mp[t1], y = mp[t2]; 36 G1[x][y] = 1; 37 } 38 floyd(); 39 for (int i = 1; i <= m; i++) { 40 int id = mp[str1[i]]; 41 for (int j = 1; j <= n; j++) G2[i][j] = G1[id][j]; 42 if (id <= n) G2[i][i] = 1; 43 } 44 } 45 46 bool is_match(int i) { 47 for(int j = 1; j <= n; j++) { 48 if(G2[i][j] && !used[j]) { 49 used[j] = 1; 50 if(from[j] == -1 || is_match(from[j])) { 51 from[j] = i; 52 return true; 53 } 54 } 55 } 56 return false; 57 } 58 59 int max_match() { 60 int ret = 0; 61 memset(from, -1, sizeof(from)); 62 for(int i = 1; i <= m; i++) { 63 memset(used, 0, sizeof(used)); 64 if(is_match(i)) ret++; 65 } 66 return ret; 67 } 68 69 int main() { 70 //freopen("case.in", "r", stdin); 71 int T; 72 scanf("%d", &T); 73 while (T--) { 74 init_graph(); 75 cout << m - max_match() << endl;; 76 if (T) puts(""); 77 } 78 return 0; 79 }
代码君
例题11-8 uva 11082
题意:给你R x C的矩阵,然后给出R个数,代表前i行的所有数之和,给出C个数,代表前j列的所有数之和,保证有解,让你构造出这个矩阵,每个数的范围是1-20。
题解:这里我们充分利用1-20这个条件,先把矩阵变成一个二分图,左边是行,右边是列,然后这是一个完全二分图,每一条边代表一个点(i,j)然后他们权值的范围还1-20,因为可能存在有些没有流,所以我们将每条边的流量限制为19,然后设置一个超级源点s到每一行,流量为r[i] - c,因为每个元素都减了一,然后就是设置一个超级汇点t,列元素流向t,流量限制为c[i] - r,最后求一个最大流之后,由于保证有解,所以最后结果一定是所有的∑c[i]-c,怎么找到解呢?记录每条列元素到行元素的编号,然后他们的流量 + 1就是答案。
1 /*zhen hao*/ 2 #include <bits/stdc++.h> 3 using namespace std; 4 5 const int maxn = 100; 6 const int inf = 1e9; 7 int r[maxn], c[maxn], no[maxn][maxn]; 8 int R, C; 9 10 struct Edge { 11 int from, to, cap, flow; 12 }; 13 14 struct EK { 15 int n, m; 16 vector<Edge> edges; 17 vector<int> G[maxn]; 18 int a[maxn]; 19 int p[maxn]; 20 21 void init(int n) { 22 for (int i = 0; i < n; i++) G[i].clear(); 23 edges.clear(); 24 } 25 26 void add_edge(int u, int v, int cap) { 27 edges.push_back((Edge){u, v, cap, 0}); 28 edges.push_back((Edge){v, u, 0, 0}); 29 m = edges.size(); 30 G[u].push_back(m - 2); 31 G[v].push_back(m - 1); 32 } 33 34 int max_flow(int s, int t) { 35 int flow = 0; 36 for (;;) { 37 memset(a, 0, sizeof a); 38 queue<int> Q; 39 Q.push(s); 40 a[s] = inf; 41 while (!Q.empty()) { 42 int x = Q.front(); Q.pop(); 43 for (int i = 0; i < (int)G[x].size(); i++) { 44 Edge& e = edges[G[x][i]]; 45 if (!a[e.to] && e.cap > e.flow) { 46 p[e.to] = G[x][i]; 47 a[e.to] = min(a[x], e.cap - e.flow); 48 Q.push(e.to); 49 } 50 } 51 if (a[t]) break; 52 } 53 if (!a[t]) break; 54 for (int u = t; u != s; u = edges[p[u]].from) { 55 edges[p[u]].flow += a[t]; 56 edges[p[u] ^ 1].flow -= a[t]; 57 } 58 flow += a[t]; 59 } 60 return flow; 61 } 62 }; 63 64 EK ek; 65 66 int main() { 67 //freopen("case.in", "r", stdin); 68 int T, tcase = 0; 69 scanf("%d", &T); 70 while (T--) { 71 scanf("%d%d", &R, &C); 72 for (int i = 1; i <= R; i++) scanf("%d", &r[i]); 73 for (int i = 1; i <= C; i++) scanf("%d", &c[i]); 74 for (int i = R; i > 1; i--) r[i] -= r[i - 1]; 75 for (int i = C; i > 1; i--) c[i] -= c[i - 1]; 76 ek.init(R + C + 2); 77 for (int i = 1; i <= R; i++) { 78 ek.add_edge(0, i, r[i] - C); 79 } 80 for (int i = 1; i <= R; i++) { 81 for (int j = 1; j <= C; j++) { 82 ek.add_edge(i, j + R, 19); 83 no[i][j] = ek.edges.size() - 2; 84 } 85 } 86 for (int i = 1; i <= C; i++) { 87 ek.add_edge(i + R, R + C + 1, c[i] - R); 88 } 89 ek.max_flow(0, R + C + 1); 90 printf("Matrix %d\n", ++tcase); 91 for (int i = 1; i <= R; i++) { 92 for (int j = 1; j <= C; j++) { 93 printf("%d ", ek.edges[no[i][j]].flow + 1); 94 } 95 puts(""); 96 } 97 puts(""); 98 } 99 return 0; 100 }
代码君
例题11-9 uva 1658
题意:给你一个有向带权图,n个点,e条边,然后让你找两条从1到n的路径,使得这两条路径之和最小,要求这两条路径不能有点重复访问。
题解:这题用到了拆点法。首先如果单纯地求流量为2的最小费用会有问题,可能出现一个点访问两次,例如下图:
X点被访问了两次,所以不满足条件,限制流量可以使得一条边只被访问一次,因为一条边的流量为1,不能限制一个点只被访问一次,所以要用拆点法,把一个点拆成两个点,然后这两个点相连,流量为1,费用为0,这样就能够避免一个点被访问两次,因为一个点i到自己的另外一个点i’只需要费用为0,所以一定会走到这条边,然后它所得到的流量为1,所以只能流向一条边,不可能流向两条边,所以上图中x点流向两条边是不可能的,所以就很好地避免了一个点被访问两次,紫书上提醒:解决结点流量的通用方法就是拆点,也就是为了限制一个结点的固定流量,要利用拆点法。
1 /*zhen hao*/ 2 #include <bits/stdc++.h> 3 using namespace std; 4 5 typedef long long ll; 6 const int maxn = 2e3 + 100; 7 const int inf = 1e9; 8 9 struct Edge { 10 int from, to, cap, flow, cost; 11 }; 12 13 struct MCMF { 14 int n, m; 15 vector<Edge> edges; 16 vector<int> G[maxn]; 17 int inq[maxn]; 18 int d[maxn]; 19 int a[maxn]; 20 int p[maxn]; 21 22 void init(int n) { 23 this->n = n; 24 for (int i = 0; i < n; i++) G[i].clear(); 25 edges.clear(); 26 } 27 28 void add_edge(int from, int to, int cap, int cost) { 29 edges.push_back((Edge){from, to, cap, 0, cost}); 30 edges.push_back((Edge){to, from, 0, 0, -cost}); 31 m = edges.size(); 32 G[from].push_back(m - 2); 33 G[to].push_back(m - 1); 34 } 35 36 bool BF(int s, int t, int flow_limit, int& flow, ll& cost) { 37 for (int i = 0; i < n; i++) d[i] = inf; 38 memset(inq, 0, sizeof inq); 39 d[s] = 0; inq[s] = 1; p[s] = 0; a[s] = inf; 40 41 queue<int> Q; 42 Q.push(s); 43 while (!Q.empty()) { 44 int u = Q.front(); Q.pop(); 45 inq[u] = 0; 46 for (int i = 0; i < (int)G[u].size(); i++) { 47 Edge& e = edges[G[u][i]]; 48 if (e.cap > e.flow && d[e.to] > d[u] + e.cost) { 49 d[e.to] = d[u] + e.cost; 50 p[e.to] = G[u][i]; 51 a[e.to] = min(a[u], e.cap - e.flow); 52 if (!inq[e.to]) { 53 inq[e.to] = 1; 54 Q.push(e.to); 55 } 56 } 57 } 58 } 59 if (d[t] == inf) return false; 60 if (flow + a[t] > flow_limit) { a[t] = flow_limit - flow; } 61 flow += a[t]; 62 cost += 1ll * d[t] * a[t]; 63 for (int u = t; u != s; u = edges[p[u]].from) { 64 edges[p[u]].flow += a[t]; 65 edges[p[u] ^ 1].flow -= a[t]; 66 } 67 return true; 68 } 69 70 int min_cost_max_flow(int s, int t, int flow_limit, ll & cost) { 71 int flow = 0; cost = 0; 72 while (flow < flow_limit && BF(s, t, flow_limit, flow, cost)); 73 return flow; 74 } 75 }; 76 77 MCMF g; 78 79 int main() { 80 //freopen("case.in", "r", stdin); 81 int n, e; 82 while (~scanf("%d%d", &n, &e)) { 83 g.init(2 * n - 2); 84 for (int i = 0; i < e; i++) { 85 int u, v, c; 86 scanf("%d%d%d", &u, &v, &c); 87 if (u != 1 && u != n) u += n - 2; 88 else u--; 89 v--; 90 g.add_edge(u, v, 1, c); 91 } 92 for (int i = 2; i <= n - 1; i++) { 93 g.add_edge(i - 1, i + n - 2, 1, 0); 94 } 95 ll ans; 96 g.min_cost_max_flow(0, n - 1, 2, ans); 97 cout << ans << endl; 98 } 99 return 0; 100 }
代码君
例题11-10 uva 1349
题意:给你一个有向图,让你找若干个圈,要求每个点都仅有一个圈包住,并且这些圈的权值之和最小,如果无解就输出N。
题解:这里用到了一种比较抽象的思考方法,不能单纯地想怎么找这个圈,我们想既然一个点在特定的一个圈内,并且是个有向图,那么必然是只有一个后继,然后每个点都只有一个后继,如果每个点都找得到这样的只有一个后继,那么就说明存在解,反之就是不存在。这个模型对应的就是二分图模型,左边有n个点,右边有n个点,左边到右边只有一个值,所以只需要求一次最小完美匹配即可。注意要处理重边。
这里我用了KM算法和费用流来编写:
1 /*zhen hao*/ 2 #include <bits/stdc++.h> 3 using namespace std; 4 5 const int maxn = 1e2 + 10; 6 const int inf = 1 << 29; 7 int W[maxn][maxn], n; 8 int Lx[maxn], Ly[maxn]; 9 int from[maxn]; 10 bool S[maxn], T[maxn]; 11 12 bool isMatch(int i) { 13 S[i] = true; 14 for (int j = 1; j <= n; j++) if (Lx[i] + Ly[j] == W[i][j] && !T[j]) { 15 T[j] = true; 16 if (!from[j] || isMatch(from[j])) { 17 from[j] = i; 18 return true; 19 } 20 } 21 return false; 22 } 23 24 void update() { 25 int a = 1 << 30; 26 for (int i = 1; i <= n; i++) if (S[i]) 27 for (int j = 1; j <= n; j++) if (!T[j]) 28 a = min(a, Lx[i] + Ly[j] - W[i][j]); 29 for (int i = 1; i <= n; i++) { 30 if (S[i]) Lx[i] -= a; 31 if (T[i]) Ly[i] += a; 32 } 33 } 34 35 void KM(int& ret) { 36 for (int i = 1; i <= n; i++) { 37 from[i] = Lx[i] = Ly[i] = 0; 38 for (int j = 1; j <= n; j++) 39 Lx[i] = max(Lx[i], W[i][j]); 40 } 41 for (int i = 1; i <= n; i++) { 42 for (;;) { 43 for (int j = 1; j <= n; j++) S[j] = T[j] = 0; 44 if (isMatch(i)) break; 45 else update(); 46 } 47 } 48 for (int i = 1; i <= n; i++) { 49 ret += W[from[i]][i]; 50 } 51 } 52 53 int main() { 54 //freopen("case.in", "r", stdin); 55 while (scanf("%d", &n) == 1 && n) { 56 for (int i = 1; i <= n; i++) 57 for (int j = 1; j <= n; j++) 58 W[i][j] = -inf; 59 for (int u = 1; u <= n; u++) { 60 int v, w; 61 while (scanf("%d", &v) && v) { 62 scanf("%d", &w); w = -w; 63 W[u][v] = max(W[u][v], w); 64 } 65 } 66 int ans = 0; 67 KM(ans); 68 if (ans <= -inf) printf("N\n"); 69 else printf("%d\n", -ans); 70 } 71 return 0; 72 }
代码君(KM)
1 /*zhen hao*/ 2 #include <bits/stdc++.h> 3 using namespace std; 4 5 const int maxn = 1e3 + 10; 6 const int inf = 1e9; 7 8 struct Edge { 9 int from, to, cap, flow, cost; 10 }; 11 12 struct MCMF { 13 int n, m; 14 vector<Edge> edges; 15 vector<int> G[maxn]; 16 int inq[maxn]; 17 int d[maxn]; 18 int a[maxn]; 19 int p[maxn]; 20 21 void init(int n) { 22 this->n = n; 23 for (int i = 0; i < n; i++) G[i].clear(); 24 edges.clear(); 25 } 26 27 void add_edge(int from, int to, int cap, int cost) { 28 edges.push_back((Edge){from, to, cap, 0, cost}); 29 edges.push_back((Edge){to, from, 0, 0, -cost}); 30 m = edges.size(); 31 G[from].push_back(m - 2); 32 G[to].push_back(m - 1); 33 } 34 35 bool BF(int s, int t, int& flow, int& cost) { 36 for (int i = 0; i < n; i++) d[i] = inf; 37 memset(inq, 0, sizeof inq); 38 d[s] = 0; inq[s] = 1; p[s] = 0; a[s] = inf; 39 40 queue<int> Q; 41 Q.push(s); 42 while (!Q.empty()) { 43 int u = Q.front(); Q.pop(); 44 inq[u] = 0; 45 for (int i = 0; i < (int)G[u].size(); i++) { 46 Edge& e = edges[G[u][i]]; 47 if (e.cap > e.flow && d[e.to] > d[u] + e.cost) { 48 d[e.to] = d[u] + e.cost; 49 p[e.to] = G[u][i]; 50 a[e.to] = min(a[u], e.cap - e.flow); 51 if (!inq[e.to]) { 52 inq[e.to] = 1; 53 Q.push(e.to); 54 } 55 } 56 } 57 } 58 if (d[t] == inf) return false; 59 flow += a[t]; 60 cost += 1ll * d[t] * a[t]; 61 for (int u = t; u != s; u = edges[p[u]].from) { 62 edges[p[u]].flow += a[t]; 63 edges[p[u] ^ 1].flow -= a[t]; 64 } 65 return true; 66 } 67 68 int min_cost_max_flow(int s, int t, int & cost) { 69 int flow = 0; cost = 0; 70 while (BF(s, t, flow, cost)); 71 return flow; 72 } 73 }; 74 75 MCMF g; 76 77 int main() { 78 //freopen("case.in", "r", stdin); 79 int n; 80 while (scanf("%d", &n) == 1 && n) { 81 g.init(2 * n + 2); 82 for (int i = 1; i <= n; i++) { 83 int v, w; 84 while (scanf("%d", &v) && v) { 85 scanf("%d", &w); 86 g.add_edge(i, v + n, 1, w); 87 } 88 } 89 for (int i = 1; i <= n; i++) { 90 g.add_edge(0, i, 1, 0); 91 g.add_edge(n + i, 2 * n + 1, 1, 0); 92 } 93 int ans; 94 if (g.min_cost_max_flow(0, 2 * n + 1, ans) == n) printf("%d\n", ans); 95 else printf("N\n"); 96 } 97 return 0; 98 }
代码君(费用流)