第11章例题(紫书)
10/15
这几天先专心刷一下图论的基础题目,也蛮多的,慢慢来。。。
例题11-1 uva 12219
题意:给你一个表达式,然后又一些子树在之前重复出现过,先要你给这些子树出现的顺序编个号1.。。N,然后如果重复出现就用编号替代,输出替代之后的表达式。
题解:这是一个表达式树的问题,显示建树,如果让我来写的话,很麻烦,搞不好复杂度是O(n^2),因为字符串是一直扫描下去的,所以就利用一个指针作为全局,然后一直扫下去,就忽略一个左括号,建左树,然后忽略逗号,建右树,忽略右括号,然后一直扫下去,就可以在O(n)时间内把整棵树给建好。接下来就是怎么记录一颗子树,紫书上利用的是一个结构体(s,l,r)s代表这个串,其实如果比较串的话会很费时间,lrj做了一个处理,就是hash成一个值来存,l和r放的是这个树的编号,然后在map里面进行映射。
这里我学到了不少技巧:hash减少复杂度,定义map的比较函数,利用建树的特点进行优化。。。。详见代码。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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
题意:给你一个图,让你选一个生成树使得生成树中的最大边和最小边的差值最小。
题解:由于边数很小,所以暴力找即可。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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;我们只需要小球一次最小生成树,然后记录这里的所有边,然后就枚举套餐,把套餐的所有点都加进并查集,然后在把生成树中的点加进并查集,去一个最小值就是答案。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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算法就很容易理解了。。。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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都有可行路径的时候才可以转移。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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的最短路,如果直接把图建出来的话,节点数太多了,有很多没有用的节点,所以我们就边搜索边枚举,也就是当要扩展一个节点的时候就遍历每一个补丁,看有哪些状态会生成,然后在进行找最短路。可扩展的状态少,虽然节点数多,所以勉强可以跑出结果。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/*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;
}
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
例题11-7 uva 753
题意:给你n个插座,m个设备,k个转换器,首先是m个设备有设备名字和插头类型组成,所以设备当然是插在插座上的,然后转换器就是插座->插头,因为设备的B插头可以查到B->C的转换器,然后变成C插头的,并且转换器可以用无限个,最后问你n个插座有多少个是没插任何设备的。
题解:有两种做法;
第一种是网络流的做法,首先是超级源点s连接着设备,表示可以转移到设备,实际上连的是插头,然后是插头连着转换器,如果插头a可以变成插头b,那么就是连一条边,流量为无穷,最后还对应的插头连着插座,流量为1,最后求一个最大流就代表有设备的插头的最大数,n-最大数就是答案。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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-最大数就是答案。还要注意一个地方就是设备是可以连到原来的插头的,但是不存在对应这种插头的插座是不能连的,所以要注意连边。。。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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就是答案。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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点流向两条边是不可能的,所以就很好地避免了一个点被访问两次,紫书上提醒:解决结点流量的通用方法就是拆点,也就是为了限制一个结点的固定流量,要利用拆点法。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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算法和费用流来编写:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }