POJ 2987 Firing【最小割】

题意:给出一个有向图,选择一个点,则要选择它的可以到达的所有节点。选择每个点有各自的利益或损失。求最大化的利益,以及此时选择人数的最小值。

算法:构造源点s汇点t,从s到每个正数点建边,容量为利益。每个负点到t建边,容量为损失的绝对值。其他关系边容量正向无穷,反向0。正数点总和减去最小割即为最大权闭合图答案。因为残余网络不会对0流边处理,所以不会将0流点选入取点集,所以最小割的取法中为被选中的点。

最大权闭合图的求解方法:

  1. 先构造网络流N,添加源点s,从s到正权值点做一条边,容量为点的权值。

  2. 添加汇点t,从负权值点到t做一条边,容量为点的权值的绝对值。

  3. 原来的边的容量统统设为无穷大。比如:

    转换为

  4. 求解最小割:最大权=正权值之和-最小割权值

  5. 残余网络中的点的个数即为裁员个数。

思路:最大权闭合图。用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\n",cnt-1,ans);
128     return 0;
129 }

参考自:hankcs

猜你喜欢

转载自www.cnblogs.com/demian/p/9234825.html
今日推荐