挑战程序设计竞赛 3.5.1 最大流(部分)
《挑战程序设计竞赛》课后练习题解集——3.5 借助水流解决问题的网络流
网络流相关问题有几个模型的定理是必须先掌握的:相关证明参考胡伯涛的《最小割模型在信息学竞赛中的应用》
1. 最大权闭合图
正点权连s,容量为点权;负点权连t,容量为点权的绝对值;原图的边保留,容量为正无穷。网络N取最小割[S, T]时,对应的闭合图V1是最大权闭合图,最大权wi = 所有正点权之和 - 最小割。
未解:V1的点的数量是最少的吗,为什么?
2.1 最大密度子图
法1:类似01分数规划,当取了一条边时,它的端点也一定要取,可以转化为二分图的最大权闭合图。子图的密度差 ≥ 1 / n ^ 2。
法2:增加源点s,汇点t,原图无向边替换成两条容量为1的有向边,s到每个点v连容量为U的边,v向t连容量为U+2g-dv,g为二分答案的猜测值,U为使容量为正数的调整值,可取边数m。N取最小割[S, T]时,s在残余网络能到达的点就是子图V', c[S, T] = U×n - 2 (E' - g×V'), 即h(g) = (U×n - c[S, T]) / 2.
2.2 带边权图
每条边有权值wi≥0,密度为边权除以点数。度数dv改为与点v相连的点的权值和,原图中的边容量为边权,U为边权和。h(g)不变。
2.3 点边均带权图
点具有点权pv(可正可负),每条边有权值wi≥0,密度为边权+点权除以点数。在2.2基础上,g改为g-pv,U为点权绝对值和的两倍与总边权和的和。
2.4(例题 最大获利) 最大点边均带权子图
(点为负权,边为正权)2.3基础上g-pv改为-pv(原论文为pv,那是pv为正数)
2.5 总结
化归为“最大化 边权和 - 点权×系数”的模型
最大流和最小割
POJ 3713 给出一个无向图,N ≤ 500, M ≤ 20000,问是否满足对于图中任意两点ab,都有至少三条路径连接ab,并且这些路径只在ab点重合。
这题可能真的不是用网络流,网上相关做法很少,而且全都认为只要判断从0出发就可以了,这是不对的。这里给出一组数据
1 6 11
2 0 1
3 0 2
4 0 3
5 0 4
6 0 5
7 1 2
8 1 3
9 2 3
10 3 4
11 3 5
12 4 5
回头用tarjan填一下这个坑。
POJ 2987 有n个员工,有m个从属关系,n (0 < n ≤ 5000), m (0 ≤ m ≤ 60000)。解雇某一员工会获利或受损bi,并且他的下属也会被解雇。问最大获利及最大获利下解雇员工的最少数量。
最大权闭合图模板题。
1 #include <cstdio>
2 #include <cstring>
3 #include <iostream>
4 #include <queue>
5 #include <vector>
6 using namespace std;
7 #define ll long long
8 #define mem(x, k) memset(x, k, sizeof(x))
9 const int V = 5e3 + 5, E = 6e4 * 2 + 5;
10 const ll INF = 0x7f7f7f7f7f7f7f7f;
11
12 int n, m, u, v, ans;
13 struct edge {
14 int to, rev;
15 ll cap;
16 };
17 vector<edge> G[V];
18 int level[V];
19 int iter[V];
20 int vis[V];
21
22 ll min(ll x, ll y) { return x < y ? x : y; }
23
24 void add_edge(int from, int to, ll cap) {
25 G[from].push_back((edge){to, (int)G[to].size(), cap});
26 G[to].push_back((edge){from, (int)(G[from].size() - 1), 0});
27 }
28
29 void bfs(int s) {
30 memset(level, -1, sizeof(level));
31 queue<int> que;
32 level[s] = 0;
33 que.push(s);
34 while (!que.empty()) {
35 int v = que.front();
36 que.pop();
37 for (int i = 0; i < G[v].size(); i++) {
38 edge &e = G[v][i];
39 if (e.cap > 0 && level[e.to] < 0) {
40 level[e.to] = level[v] + 1;
41 que.push(e.to);
42 }
43 }
44 }
45 }
46
47 ll dfs(int v, int t, ll f) { //起点, 终点, 当前流量
48 if (v == t) return f;
49 for (int &i = iter[v]; i < G[v].size(); i++) {
50 edge &e = G[v][i];
51 if (e.cap > 0 && level[v] < level[e.to]) {
52 ll d = dfs(e.to, t, min(f, e.cap));
53 if (d > 0) {
54 e.cap -= d;
55 G[e.to][e.rev].cap += d;
56 return d;
57 }
58 }
59 }
60 return 0;
61 }
62
63 ll max_flow(int s, int t) {
64 ll flow = 0;
65 for (;;) {
66 bfs(s);
67 if (level[t] < 0) return flow;
68 memset(iter, 0, sizeof(iter));
69 ll f;
70 while ((f = dfs(s, t, INF)) > 0) {
71 flow += f;
72 }
73 }
74 }
75
76 void dfs_(int v) {
77 vis[v] = 1;
78 ans++;
79 for (int i = 0; i < G[v].size(); i++) {
80 edge e = G[v][i];
81 if (vis[e.to] || e.cap <= 0) continue;
82 dfs_(e.to);
83 }
84 }
85
86 int main() {
87 ll x, sum = 0;
88 scanf("%d %d", &n, &m);
89 for (int i = 1; i <= n; i++) {
90 scanf("%lld", &x);
91 if (x > 0) {
92 sum += x;
93 add_edge(0, i, x);
94 }
95 if (x < 0) add_edge(i, n + 1, -x);
96 }
97 for (int i = 0; i < m; i++) {
98 scanf("%d%d", &u, &v);
99 add_edge(u, v, INF);
100 }
101 ll res = sum - max_flow(0, n + 1);
102 dfs_(0);
103 printf("%d %lld\n", ans - 1, res);
104 }
POJ 2914 全局最小割模板题
有关证明找了还久还是没有比较满意的……最后发现洛谷有模板题,以及看起来还不错的题解,但是不想花时间看了,留个坑以后看证明。
这个板子通过洛谷数据,跑得也算比较快的了,应该比较可靠。复杂度O(N^3)
1 #include <cstdio>
2 #include <cstring>
3 #include <iostream>
4 using namespace std;
5 #define mem(x, k) memset(x, k, sizeof(x))
6 const int inf = 0x3f3f3f3f;
7 const int maxn = 605;
8
9 int n, m;
10 int cap[maxn][maxn], dis[maxn], par[maxn], vis[maxn];
11
12 int stoer_wanger(int n) {
13 int res = inf;
14 for (int i = 0; i < n; i++) par[i] = i;
15 while (n > 1) {
16 int k, pre = 0;
17 mem(vis, 0);
18 mem(dis, 0);
19 vis[par[0]] = 1;
20 for (int i = 1; i < n; i++) {
21 k = -1;
22 for (int j = 1; j < n; j++) {
23 if (!vis[par[j]]) {
24 dis[par[j]] += cap[par[pre]][par[j]];
25 if (k == -1 || dis[par[k]] < dis[par[j]]) k = j;
26 }
27 }
28 vis[par[k]] = 1;
29 if (i == n - 1) {
30 res = min(res, dis[par[k]]);
31 for (int j = 0; j < n; j++) {
32 cap[par[pre]][par[j]] += cap[par[j]][par[k]];
33 cap[par[j]][par[pre]] += cap[par[j]][par[k]];
34 }
35 par[k] = par[--n];
36 }
37 pre = k;
38 }
39 }
40 return res;
41 }
42
43 int main() {
44 int u, v, c;
45 while (scanf("%d %d", &n, &m) != EOF) {
46 memset(cap, 0, sizeof(cap));
47 for (int i = 0; i < m; i++) {
48 scanf("%d %d %d", &u, &v, &c);
49 cap[u][v] += c;
50 cap[v][u] += c;
51 }
52 printf("%d\n", stoer_wanger(n));
53 }
54 }


浙公网安备 33010602011771号