最大权闭合子图
所谓闭合子图就是给定一个有向图,从中选择一些点组成一个点集V。对于V中任意一个点,其后续节点都仍然在V中。比如:
在这个图中有8个闭合子图:∅, {3}, {4}, {2,4}, {3,4}, {1,3,4}, {2,3,4}, {1,2,3,4}
最大权闭合子图即为:在一个图中权值最大的子图;
下面以一道求最大权闭合子图的模板题目为例:
eg:Hiho Coder 1398(求最大权闭合子图)
周末,小Hi和小Ho所在的班级决定举行一些班级建设活动。
根据周内的调查结果,小Hi和小Ho一共列出了N项不同的活动(编号1..N),第i项活动能够产生a[i]的活跃值。
班级一共有M名学生(编号1..M),邀请编号为i的同学来参加班级建设活动需要消耗b[i]的活跃值。
每项活动都需要某些学生在场才能够进行,若其中有任意一个学生没有被邀请,这项活动就没有办法进行。
班级建设的活跃值是活动产生的总活跃值减去邀请学生所花费的活跃值。
小Hi和小Ho需要选择进行哪些活动,来保证班级建设的活跃值尽可能大。
比如有3项活动,4名学生:
第1项活动产生5的活跃值,需要编号为1、2的学生才能进行;
第2项活动产生10的活跃值,需要编号为3、4的学生才能进行;
第3项活动产生8的活跃值,需要编号为2、3、4的学生才能进行。
编号为1到4的学生需要消耗的活跃值分别为6、3、5、4。
假设举办活动集合为{1},需要邀请的学生集合为{1,2},则得到的班级活跃值为5-9 = -4。
假设举办活动集合为{2},需要邀请的学生集合为{3,4},则得到的班级活跃值为10-9 = 1。
假设举办活动集合为{2,3},需要邀请的学生集合为{2,3,4},则得到的班级活跃值为18-12 = 6。
假设举办活动集合为{1,2,3},需要邀请的学生集合为{1,2,3,4},则得到的班级活跃值为23-18 = 5。
小Hi和小Ho总是希望班级活跃值越大越好,因此在这个例子中,他们会选择举行活动2和活动3。
我们先把这次的问题转化为2分图。将N个活动看作A部,将M个学生看作B部。若第i个活动需要第j个学生,就连一条从A[i]到B[j]的有向边。比如对于例子:
假如选择A[1],则我们需要同时选择B[1],B[2]。那么选择什么活动和其需要的学生,是不是就刚好对应了这个图中的一个闭合子图呢?
如果把活跃值算作权值,A部的节点包含有正的权值,B部的节点是负的权值。那么我们要求的也就是一个权值最大的闭合子图;
对于一般的图来说:首先建立源点s和汇点t,将源点s与所有权值为正的点相连,容量为权值;将所有权值为负的点与汇点t相连,容量为权值的绝对值;
权值为0的点不做处理;同时将原来的边容量设置为无穷大。举个例子:
对于我们题目中的例子来说,其转化的网络流图为:
上图中黑边表示容量无穷大的边。
结论:最大权闭合子图的权值等于所有正权点之和减去最小割。(可以直接记结论,比赛也没人让你证明结论 Orz )
证明如下:
1. 最小割一定是简单割
简单割指得是:割(S,T)中每一条割边都与s或者t关联,这样的割叫做简单割。
因为在图中将所有与s相连的点放入割集就可以得到一个割,且这个割不为正无穷。而最小割一定小于等于这个割,所以最小割一定不包含无穷大的边。因此最小割一定一个简单割。
2. 简单割一定和一个闭合子图对应
闭合子图V和源点s构成S集,其余点和汇点t构成T集。
首先证明闭合子图是简单割:若闭合子图对应的割(S,T)不是简单割,则存在一条边(u,v),u∈S,v∈T,且c(u,v)=∞。说明u的后续节点v不在S中,产生矛盾。
接着证明简单割是闭合子图:对于V中任意一个点u,u∈S。u的任意一条出边c(u,v)=∞,不会在简单割的割边集中,因此v不属于T,v∈S。所以V的所有点均在S中,因此S-s是闭合子图。
由上面两个引理可以知道,最小割也对应了一个闭合子图,接下来证明最小割就是最大权的闭合子图。
首先有割的容量C(S,T)=T中所有正权点的权值之和+S中所有负权点的权值绝对值之和。
闭合子图的权值W=S中所有正权点的权值之和-S中所有负权点的权值绝对值之和。
则有C(S,T)+W=T中所有正权点的权值之和+S中所有正权点的权值之和=所有正权点的权值之和。
所以W=所有正权点的权值之和-C(S,T)
由于所有正权点的权值之和是一个定值,那么割的容量越小,W也就越大。因此当C(S,T)取最小割时,W也就达到了最大权。
题目参考代码(可以作为模板):
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<string> 5 #include<queue> 6 #include<vector> 7 #include<stack> 8 #include<map> 9 #include<set> 10 #include<cmath> 11 #include<algorithm> 12 using namespace std; 13 typedef long long LL; 14 #define PI acos(-1.0) 15 #define eps 1e-8 16 #define mem(a,b) memset(a,b,sizeof a) 17 const int INF=0x3f3f3f3f; 18 const LL inf=0x3f3f3f3f3f3f3f3fLL; 19 typedef pair<int,int> P; 20 const int maxn=2010; 21 int n,m,s,t,a,k,val; 22 struct Edge{ 23 int from,to,cap,flow; 24 Edge(int _f,int _t,int _c,int _fl):from(_f),to(_t),cap(_c),flow(_fl) { } 25 }; 26 vector<Edge> edges; 27 vector<int> G[maxn]; 28 bool vis[maxn]; 29 int d[maxn],cur[maxn]; 30 31 void Init() 32 { 33 mem(d,0); 34 for(int i=0;i<=n;i++) G[i].clear(); 35 } 36 37 void Addedge(int from,int to,int cap) 38 { 39 edges.push_back(Edge(from,to,cap,0)); 40 edges.push_back(Edge(to,from,0,0)); 41 int m=edges.size(); 42 G[from].push_back(m-2); G[to].push_back(m-1); 43 } 44 45 bool bfs() 46 { 47 mem(vis,0); 48 queue<int> q; 49 q.push(s); 50 d[s]=0; vis[s]=1; 51 while(!q.empty()) 52 { 53 int x=q.front();q.pop(); 54 for(int i=0;i<G[x].size();i++) 55 { 56 Edge &e=edges[G[x][i]]; 57 if(!vis[e.to] && e.cap>e.flow) 58 { 59 vis[e.to]=1; 60 d[e.to]=d[x]+1; 61 q.push(e.to); 62 } 63 } 64 } 65 return vis[t]; 66 } 67 68 int dfs(int x,int a) 69 { 70 if(x==t || a==0) return a; 71 int flow=0,f; 72 for(int &i=cur[x];i<G[x].size();++i) 73 { 74 Edge &e=edges[G[x][i]]; 75 if(d[e.to]==d[x]+1 && (f=dfs(e.to,min(a,e.cap-e.flow)))>0) 76 { 77 e.flow+=f; 78 edges[G[x][i]^1].flow-=f; 79 flow+=f; a-=f; 80 if(a==0) break; 81 } 82 } 83 return flow; 84 } 85 86 int Maxflow(int s,int t) 87 { 88 int flow=0; 89 while(bfs()) 90 { 91 mem(cur,0); 92 flow+=dfs(s,INF); 93 } 94 return flow; 95 } 96 97 int main() 98 { 99 ios::sync_with_stdio(false); 100 cin>>n>>m; 101 int sum=0,num; 102 for(int i=1;i<=m;i++) cin>>val,Addedge(n+i,m+n+1,val); 103 for(int i=1;i<=n;i++) 104 { 105 cin>>val>>k; 106 sum+=val; 107 Addedge(0,i,val); 108 for(int j=1;j<=k;j++) 109 { 110 cin>>num; 111 Addedge(i,n+num,INF); 112 } 113 } 114 s=0,t=n+m+1; 115 int ans=sum-Maxflow(s,t); 116 cout<<ans<<endl; 117 return 0; 118 }