挑战程序设计竞赛 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
View Code

回头用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 }
View Code

 

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 }
View Code

 

posted @ 2020-02-19 16:28  Linqi05  阅读(2)  评论(0)    收藏  举报