第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的比较函数,利用建树的特点进行优化。。。。详见代码。

 

 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
", 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:
", ++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
", ++tcase);
31     while (q--) {
32       int u, v;
33       scanf("%d%d", &u, &v);
34       if (G[u][v] != INF)
35         printf("%d
", 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
", ++tcase);
    dijkstra((1 << n) - 1);
    if (d[0] != INF)  printf("Fastest sequence takes %d seconds.

", d[0]);
    else puts("Bugs cannot be fixed.
");
  }
  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
", ++tcase);
56     if (ans < 0) puts("Bugs cannot be fixed.
");
57     else printf("Fastest sequence takes %d seconds.

", 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
", ++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
");
69     else printf("%d
", -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
", ans);
95     else printf("N
");
96   }
97   return 0;
98 }
代码君(费用流)
原文地址:https://www.cnblogs.com/zhenhao1/p/5548123.html