定义:在一个图中,我们选取一些点构成集合,记为V,且集合中的出边(即集合中的点的向外连出的弧),所指向的终点(弧头)也在V中,则我们称V为闭合图。最大权闭合图即在所有闭合图中,集合中点的权值之和最大的V,我们称V为最大权闭合图。
做法:首先我们将其转化为一个网络(现在不要问为什么,接下来会证明用网络可以求解)。构造一个源点S,汇点T。我们将S与所有权值为正的点连一条容量为其权值的边,将所有权值为负的点与T连一条容量为其权值的绝对值的边,原来的边将其容量定为正无穷。
首先引入结论,最小割所产生的两个集合中,其源点S所在集合(除去S)为最大权闭合图,接下来我们来说明一些结论。
1.最小割为简单割。
简单割的概念:割集的每条边都与S或T关联。(请下面阅读时一定分清最小割与简单割,容易混淆)
2.网络中的简单割与原图中闭合图存在一一对应的关系。(即所有闭合图都是简单割,简单割也必定是一个闭合图)。
闭合图是简单割:如果闭合图不是简单割(反证法)。那么说明有一条边是容量为正无穷的边,则说明闭合图中有一条出边的终点不在闭合图中,矛盾。
简单割是闭合图:因为简单割不含正无穷的边,所以不含有连向另一个集合(除T)的点,所以其出边的终点都在简单割中,满足闭合图定义。
3.最小割所产生的两个集合中,其源点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)。
http://cogs.pro/cogs/problem/problem.php?pid=727
/**************************************************************************************************** 最大权闭合图—最小割 dinic算法 ********************************************************************************************************/ #include<cstdio> #include<cstring> #include<algorithm> using namespace std; #define check(c) (c>='0'&&c<='9') #define inf 9999999 struct Edge{ int next,to,power; Edge(){} Edge(int next,int to,int power):next(next),to(to),power(power){} }edge[10001]; int f[205],top=1,q[205],l,r,dis[202]; inline void add_edge(int c,int t,int p){ edge[++top]=Edge(f[c],t,p);f[c]=top; edge[++top]=Edge(f[t],c,0);f[t]=top; } inline bool in(int& a){ a=0;char c=getchar(); while(!check(c))c=getchar(); while(check(c))a=a*10+c-48,c=getchar(); return (c!=' '&&c!=' '); } bool bfs(){ memset(dis,-1,sizeof(dis));dis[0]=1; l=0,r=1,q[0]=0; while(l<r){ int v=q[l++],to=f[v],x; while(to){ x=edge[to].to; if(dis[x]==-1&&edge[to].power>0){ dis[x]=dis[v]+1,q[r++]=x; if(x==201)return 1; } to=edge[to].next; } } return 0; } int dfs(int x,int low){//low为到当前节点最大流 int ans=0; if(x==201)return low; int to=f[x],i; while(to){ i=edge[to].to; if(dis[i]==dis[x]+1&&(ans=dfs(i,min(low,edge[to].power)))){ edge[to].power-=ans;//边的最大流量更新 edge[to^1].power+=ans; return ans; } to=edge[to].next; } return ans; } int main(){ freopen("shuttle.in","r",stdin); freopen("shuttle.out","w",stdout); int n,m,x,sum=0;scanf("%d%d",&m,&n); for(int i=1;i<=m;i++){ bool d=1; while(in(x)){ if(d)add_edge(0,i,x),d=0,sum+=x; else add_edge(i,100+x,inf); } if(d)add_edge(0,i,x); else add_edge(i,100+x,inf); } for(int i=1;i<=n;i++){ in(x); add_edge(100+i,201,x); } while(bfs()){//判断是否能到n while(int tans=dfs(0,0x7fffffff))sum-=tans;//用增广路更新 } for(int i=1;i<=m;i++)if(dis[i]!=-1)printf("%d ",i);//最后t集合内的为选用的 printf(" "); for(int i=1;i<=n;i++)if(dis[i+100]!=-1)printf("%d ",i); printf(" "); printf("%d",sum); return 0; }