poj 2987(最大权闭合图)
题目链接:http://poj.org/problem?id=2987
题意:某公司想要裁员,裁员的标准是如果某人被裁,那么其下属也会被裁,依此类推,每一个人都有一个贡献度,问怎样裁员才能使得最后的贡献度最大并且裁掉人数最少?
以下内容参考自胡伯涛 《最小割模型在信息学竞赛中的应用》以及 http://www.cnblogs.com/wuyiqi/archive/2012/03/12/2391960.html
闭合图:在一个图中,我们选取一些点构成集合,记为V,且集合中的出边(即集合中的点的向外连出的弧),所指向的终点(弧头)也在V中,则我们称V为闭合图。
最大权闭合图:在所有闭合图中,集合中点的权值之和最大的V,我们称V为最大权闭合图。
上图中闭合图有
{5}、{2,5}、{4,5}
{2,4,5}、{3,4,5}
{1,2,3,4,5}、{1,2,4,5}
最大权闭合图为{3,4,5}。权值和为4.
怎样求解最大权闭合图呢??
构图:我们设立一个超级源点和一个超级汇点,从超级源点向所有权值为正的点连一条单向边,边的权值为点的权值;从所有权值为负的点向超级汇点连一条单向边,边的权值为点的权值的绝对值;然后图中所有的边都设为无穷大。得到如下图:
然后求出此题的最小割,用所有权值为正的点之和减掉最小割即为最大权闭合图。
以下是证明过程,引自http://www.cnblogs.com/wuyiqi/archive/2012/03/12/2391960.html
首先引入结论,最小割所产生的两个集合中,其源点S所在集合(除去S)为最大权闭合图,接下来我们来说明一些结论。
- 证明:最小割为简单割。
引入一下简单割的概念:割集的每条边都与S或T关联。(请下面阅读时一定分清最小割与简单割,容易混淆)
那么为什么最小割是简单割呢?因为除S和T之外的点间的边的容量是正无穷,最小割的容量不可能为正无穷。所以,得证。
- 证明网络中的简单割与原图中闭合图存在一一对应的关系。(即所有闭合图都是简单割,简单割也必定是一个闭合图)。
证明闭合图是简单割:如果闭合图不是简单割(反证法)。那么说明有一条边是容量为正无穷的边,则说明闭合图中有一条出边的终点不在闭合图中,矛盾。
证明简单割是闭合图:因为简单割不含正无穷的边,所以不含有连向另一个集合(除T)的点,所以其出边的终点都在简单割中,满足闭合图定义。得正。
- 证明最小割所产生的两个集合中,其源点S所在集合(除去S)为最大权闭合图。
首先我们记一个简单割的容量为C,且S所在集合为N,T所在集合为M。
则C=M中所有权值为正的点的权值(即S与M中点相连的边的容量)+N中所有权值为负的点权值的绝对值(即N中点与T中点相连边的容量)。记(C=x1+y1);(很好理解,不理解画一个图或想象一下就明白了)。
我们记N这个闭合图的权值和为W。
则W=N中权值为正的点的权值-N中权值为负的点的权值的绝对值。记(W=x2-y2);
则W+C=x1+y1+x2-y2。
因为明显y1=y2,所以W+C=x1+x2;
x1为M中所有权值为正的点的权值,x2为N中权值为正的点的权值。
所以x1+x2=所有权值为正的点的权值之和(记为TOT).
所以我们得到W+C=TOT.整理一下W=TOT-C.
到这里我们就得到了闭合图的权值与简单割的容量的关系。
因为TOT为定值,所以我们欲使W最大,即C最小,即此时这个简单割为最小割,此时闭合图为其源点S所在集合(除去S)。得正。
至此,我们就将最大权闭合图问题转化为了求最小割的问题。求最小割用最小割容量=最大流,即可将问题转化为求最大流的问题。
最后的结论为:最大权闭合图 :原边改为正无穷后,添加S和T,S向所有点连正权,所有点向T连负权的绝对值.求最小割,用所有权值为正的点相加之和减掉最小割得到最大权闭合图。
———————————————————————————————————————————————————————————————————————题目分割线
poj 2987:上面的证明过程已经说了:最小割所产生的两个集合中,其源点S所在集合(除去S)为最大权闭合图。所以这题的最少人数在残余网络中做一次深搜,得到的点即为最少裁员的人数,最大贡献值即为最大权闭合图。而这题的数据量达到了5000*10^7,所以要用_int64保存结果.
#include <stdio.h> #include <algorithm> #include <queue> #include <string.h> #include <math.h> #include <iostream> #include <math.h> using namespace std; typedef long long LL; const int N = 5005; const int INF = 999999999; struct Edge{ int v,next; int w; }edge[N*N]; int head[N]; int level[N]; int tot; void init(){ memset(head,-1,sizeof(head)); tot=0; } void addEdge(int u,int v,int w,int &k){ edge[k].v = v,edge[k].w=w,edge[k].next=head[u],head[u]=k++; edge[k].v = u,edge[k].w=0,edge[k].next=head[v],head[v]=k++; } int BFS(int src,int des){ queue<int >q; memset(level,0,sizeof(level)); level[src]=1; q.push(src); while(!q.empty()){ int u = q.front(); q.pop(); if(u==des) return 1; for(int k = head[u];k!=-1;k=edge[k].next){ int v = edge[k].v; int w = edge[k].w; if(level[v]==0&&w!=0){ level[v]=level[u]+1; q.push(v); } } } return -1; } int dfs(int u,int des,int increaseRoad){ if(u==des) return increaseRoad; int ret=0; for(int k=head[u];k!=-1;k=edge[k].next){ int v = edge[k].v; int w = edge[k].w; if(level[v]==level[u]+1&&w!=0){ int MIN = min(increaseRoad-ret,w); w = dfs(v,des,MIN); edge[k].w -=w; edge[k^1].w+=w; ret+=w; if(ret==increaseRoad) return ret; } } return ret; } LL Dinic(int src,int des){ LL ans = 0; while(BFS(src,des)!=-1) ans+=(LL)dfs(src,des,INF*1.0); return ans; } int ans=0; bool vis[N]; void dfs(int u){ ans++; vis[u] = true; for(int k = head[u];k!=-1;k=edge[k].next){ if(edge[k].w>0&&!vis[edge[k].v]){ dfs(edge[k].v); } } } int main(){ int n,m; while(scanf("%d%d",&n,&m)!=EOF){ init(); int src = 0,des = n+1; LL sum=0; for(int i=1;i<=n;i++){ int w; scanf("%d",&w); if(w<0) addEdge(i,des,-w,tot); else addEdge(src,i,w,tot),sum+=w; } for(int i=1;i<=m;i++){ int u,v; scanf("%d%d",&u,&v); addEdge(u,v,INF,tot); } LL MAX = sum-Dinic(src,des); memset(vis,false,sizeof(vis)); dfs(src); ans-=1; ///除去超级源点 printf("%d %I64d\n",ans,MAX); } }