POJ 2987 Firing(最大流最小割の最大权闭合图)

Description

You’ve finally got mad at “the world’s most stupid” employees of yours and decided to do some firings. You’re now simply too mad to give response to questions like “Don’t you think it is an even more stupid decision to have signed them?”, yet calm enough to consider the potential profit and loss from firing a good portion of them. While getting rid of an employee will save your wage and bonus expenditure on him, termination of a contract before expiration costs you funds for compensation. If you fire an employee, you also fire all his underlings and the underlings of his underlings and those underlings’ underlings’ underlings… An employee may serve in several departments and his (direct or indirect) underlings in one department may be his boss in another department. Is your firing plan ready now?

Input

The input starts with two integers n (0 < n ≤ 5000) and m (0 ≤ m ≤ 60000) on the same line. Next follows n + m lines. The first n lines of these give the net profit/loss from firing the i-th employee individually bi (|bi| ≤ 107, 1 ≤ i  n). The remaining m lines each contain two integers i and j (1 ≤ i, j  n) meaning the i-th employee has the j-th employee as his direct underling.

Output

Output two integers separated by a single space: the minimum number of employees to fire to achieve the maximum profit, and the maximum profit.

题目大意:有n个点,每个点有一个权值(可能为负数),有m条有向边,求一个闭合子图,要求在权值最大的情况下点最少。

PS:所谓闭合子图,即在一个图中,若选择了x,那么x的所有后继都要选上(如a→b→c→d→e,选了b,就要选c、d、e)。

思路:最大权闭合图的经典建图:源点S到所有正权的点连一条边,容量为权值,从所有负权的点连一条边到汇点T,容量为权值的绝对值。对每一条边(i, j),连一条边i→j,容量为无穷大。所有正权权值之和减去最大流(最小割)为最大权,从S遍历一遍残量网络,能遍历到的点都为闭合子图中的点,计数即可得到答案。

小证明:在残量网络中,S能遍历到的点都为所选闭合子图,记为V(s),其余记为V(T)。那么V(S)中任何一个点,它都不会有容量为无穷大的边会指向V(T)中的点,因为若有,这条边要算入最小割,而最小割不可能为无穷大,这就保证了选了点x,x的后继都会被选上。记sum为所有正权权值之和,一个简单割(不割无穷大的边)割掉的与S关联的边的容量和为cut(S),割掉的与T关联的边的容量和为cut(T),对于所有选上的点,与源点关联的点的权和应该为sum - cut(S),cut(S)都是没有选上的点;与汇点关联的选上的点的权和为cut(T),cut(T)都是选上的点。那么该子图的权和为sum - cut(S) - cut(T),即sum - (cut(S) + cut(T)),我们需要最大化前面的式子,而sum是固定值,所以需要最小化cut(S) + cut(T),这个值最小的时候,就是最小割。

关于这样选到的点一定是最少的证明可以看下面的链接,不过简单地说就是:若有多种选择,那么就有一片点,选上它们之后权值总和为0,也就是源点到它们的容量应该是满的,因为我们是在残量网络中遍历,所以它们绝对不会被遍历到。至于下面链接所说的原图是DAG,这个我没看出来哪里说了……不过我们可以把环缩成一个点,不影响证明……

http://hi.baidu.com/dispossessed/item/f75d32161ae2d8573a176e8d

代码(391MS):

  1 #include <cstdio>
  2 #include <cstring>
  3 #include <algorithm>
  4 #include <queue>
  5 using namespace std;
  6 
  7 typedef long long LL;
  8 
  9 const int MAXN = 5010;
 10 const int MAXE = 200010;
 11 const LL INF = 0x3fff3fff3fff3fffLL;
 12 
 13 struct SAP {
 14     int head[MAXN], dis[MAXN], gap[MAXN], cur[MAXN], pre[MAXN];
 15     int to[MAXE], next[MAXE];
 16     LL flow[MAXE];
 17     int ecnt, n, st, ed;
 18 
 19     void init() {
 20         memset(head, 0, sizeof(head));
 21         ecnt = 2;
 22     }
 23 
 24     void add_edge(int u, int v, LL c) {
 25         to[ecnt] = v; flow[ecnt] = c; next[ecnt] = head[u]; head[u] = ecnt++;
 26         to[ecnt] = u; flow[ecnt] = 0; next[ecnt] = head[v]; head[v] = ecnt++;
 27         //printf("%d->%d %I64d
", u, v, c);
 28     }
 29 
 30     void bfs() {
 31         memset(dis, 0x3f, sizeof(dis));
 32         queue<int> que; que.push(ed);
 33         dis[ed] = 0;
 34         while(!que.empty()) {
 35             int u = que.front(); que.pop();
 36             ++gap[dis[u]];
 37             for(int p = head[u]; p; p = next[p]) {
 38                 int &v = to[p];
 39                 if(flow[p ^ 1] && dis[v] > n) {
 40                     dis[v] = dis[u] + 1;
 41                     que.push(v);
 42                 }
 43             }
 44         }
 45     }
 46 
 47     LL Max_flow(int ss, int tt, int nn) {
 48         st = ss; ed = tt; n = nn;
 49         LL ans = 0, minFlow = INF;
 50         for(int i = 0; i <= n; ++i) {
 51             cur[i] = head[i];
 52             gap[i] = 0;
 53         }
 54         int u = pre[st] = st;
 55         bfs();
 56         while(dis[st] < n) {
 57             bool flag = false;
 58             for(int &p = cur[u]; p; p = next[p]) {
 59                 int &v = to[p];
 60                 if(flow[p] && dis[u] == dis[v] + 1) {
 61                     flag = true;
 62                     minFlow = min(minFlow, flow[p]);
 63                     pre[v] = u;
 64                     u = v;
 65                     if(u == ed) {
 66                         ans += minFlow;
 67                         while(u != st) {
 68                             u = pre[u];
 69                             flow[cur[u]] -= minFlow;
 70                             flow[cur[u] ^ 1] += minFlow;
 71                         }
 72                         minFlow = INF;
 73                     }
 74                     break;
 75                 }
 76             }
 77             if(flag) continue;
 78             int minDis = n - 1;
 79             for(int p = head[u]; p; p = next[p]) {
 80                 int &v = to[p];
 81                 if(flow[p] && dis[v] < minDis) {
 82                     minDis = dis[v];
 83                     cur[u] = p;
 84                 }
 85             }
 86             if(--gap[dis[u]] == 0) break;
 87             ++gap[dis[u] = minDis + 1];
 88             u = pre[u];
 89         }
 90         return ans;
 91     }
 92 
 93     int cnt_S() {
 94         memset(dis, 0, sizeof(dis));
 95         int * vis = dis, ret = 0;
 96         queue<int> que; que.push(st);
 97         vis[st] = 1;
 98         while(!que.empty()) {
 99             int u = que.front(); que.pop();
100             for(int p = head[u]; p; p = next[p]) {
101                 int &v = to[p];
102                 if(flow[p] && !vis[v]) {
103                     vis[v] = 1;
104                     ++ret;
105                     que.push(v);
106                 }
107             }
108         }
109         return ret;
110     }
111 } G;
112 
113 int main() {
114     int n, m, a, b;
115     LL x, sum = 0;
116     scanf("%d%d", &n, &m);
117     int ss = 0, tt = n + 1;
118     G.init();
119     for(int i = 1; i <= n; ++i) {
120         scanf("%I64d", &x);
121         if(x > 0) sum += x, G.add_edge(ss, i, x);
122         if(x < 0) G.add_edge(i, tt, -x);
123     }
124     for(int i = 1; i <= m; ++i) {
125         scanf("%d%d", &a, &b);
126         G.add_edge(a, b, INF);
127     }
128     LL ans1 = sum - G.Max_flow(ss, tt, tt);
129     int ans2 = G.cnt_S();
130     printf("%d %I64d
", ans2, ans1);
131 }
View Code
原文地址:https://www.cnblogs.com/oyking/p/3249174.html