题意:给出一个有向图,选择一个点,则要选择它的可以到达的所有节点。选择每个点有各自的利益或损失。求最大化的利益,以及此时选择人数的最小值。
算法:构造源点s汇点t,从s到每个正数点建边,容量为利益。每个负点到t建边,容量为损失的绝对值。其他关系边容量正向无穷,反向0。正数点总和减去最小割即为最大权闭合图答案。因为残余网络不会对0流边处理,所以不会将0流点选入取点集,所以最小割的取法中为被选中的点。
最大权闭合图的求解方法:
-
先构造网络流N,添加源点s,从s到正权值点做一条边,容量为点的权值。
-
添加汇点t,从负权值点到t做一条边,容量为点的权值的绝对值。
-
原来的边的容量统统设为无穷大。比如:
转换为
-
求解最小割:最大权=正权值之和-最小割权值
-
残余网络中的点的个数即为裁员个数。
思路:最大权闭合图。用Dinic求最小割
要得到最大收益,就是尽量选择更多收益为正数的人,选择更少收益为负数的人,因此我们将收益为正数的人与源点连一条边,将收益为负数的人与汇点连一条边,这样得到的割集就是未选择的收益为正数的人+选择的收益为负数的人(也可以是损失的收益),要使这个割集越小越好,那么就是求最小割。最大收益是所有正数的和sum,再用sum-最小割(损失的收益)就可以得到最大收益。
最少的裁员人数,最小割后的的残余网络中,只要能从S遍历到的点,就可以看成是被裁掉的点,因为最终肯定会遇到割边达不到T,所以我们直接DFS残余网络的点数就可以得到最少的裁员人数了。
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 #include <queue> 5 #include <vector> 6 using namespace std; 7 typedef long long LL; 8 9 const LL INF = 0x3f3f3f3f3f3f3f3f; 10 11 struct edge 12 { 13 edge(int _v,int _r, LL _c):v(_v),r(_r),c(_c){}; 14 int v,r; 15 LL c; 16 }; 17 18 vector<edge> e[5010]; 19 20 void add_edge(int u,int v,LL c) 21 { 22 e[u].push_back(edge(v,e[v].size(),c)); 23 e[v].push_back(edge(u,e[u].size()-1,0)); 24 } 25 26 int level[5010]; 27 int iter[5010]; // 当前弧,在其之前的边已经没有用了 28 queue<int> q; 29 30 bool bfs(int s,int t) 31 { 32 memset(level,0, sizeof(level)); 33 while (!q.empty()) q.pop(); 34 q.push(s); 35 level[s]=1; 36 while(!q.empty()) 37 { 38 int u=q.front();q.pop(); 39 for(int i=0;i<e[u].size();i++) 40 { 41 int v=e[u][i].v; 42 if(e[u][i].c>0&&!level[v]) 43 { 44 q.push(v); 45 level[v]=level[u]+1; 46 } 47 if(level[t])return 1; 48 } 49 } 50 return 0; 51 } 52 53 LL dfs(int s,int t, LL flow) 54 { 55 if(s==t)return flow; 56 for(int& i = iter[s];i<e[s].size();i++) // 一次增广返回时,记录当前弧 57 { 58 int v = e[s][i].v; 59 if(e[s][i].c>0&&level[v]==level[s]+1) 60 { 61 LL k = dfs(v,t,min(flow,e[s][i].c)); 62 if(k>0) 63 { 64 e[s][i].c-=k; 65 e[v][e[s][i].r].c+=k; 66 return k; 67 } 68 } 69 } 70 return 0; 71 } 72 73 LL Dinic(int s,int t) 74 { 75 LL flow=0; 76 while(bfs(s,t)) 77 { 78 LL f=0; 79 memset(iter, 0, sizeof(iter)); 80 while((f=dfs(s,t,INF))>0)flow+=f; 81 } 82 return flow; 83 } 84 85 bool vis[5010]; 86 int cnt; 87 void GaoCnt(int u)//dfs统计残余图的正边 88 { 89 ++cnt; 90 vis[u]=1; 91 for(int i=0;i<e[u].size();i++) 92 { 93 int v=e[u][i].v; 94 if(e[u][i].c>0&&!vis[v]) 95 GaoCnt(v); 96 } 97 } 98 99 100 int main() 101 { 102 int n,m; 103 scanf("%d%d",&n,&m); 104 int s=0,t=n+1;// 源点,汇点 105 LL ans=0; 106 for(int i=1;i<=n;i++) 107 { 108 LL c; 109 scanf("%lld",&c); 110 if(c>0) 111 { 112 ans+=c; //正权值求和 113 add_edge(s,i,c); 114 } 115 else if(c<0) 116 { 117 add_edge(i,t,-c); 118 } 119 } 120 for (int i = 0; i < m; ++i) { 121 int u,v; 122 scanf("%d%d",&u,&v); 123 add_edge(u,v,INF); 124 } 125 ans-= Dinic(s,t); 126 GaoCnt(s); 127 printf("%d %lld ",cnt-1,ans); 128 return 0; 129 }