闭合图:图中所有顶点的出边所指向的顶点仍然在改闭合图中,称改图为闭合图
最大权闭合图:给图中每个顶点一个权值(有正有负),闭合图的权值为闭合图中所有点的权值之和,最大权闭合图就是一个图的所有闭合图中权值大的闭合图
解法:网络流。具体请详见:胡伯涛:《最小割模型在信息学竞赛中的应用》
建图:
1、新增源点 source ,并连一条边到原图中所有权值为正的顶点,边权为顶点的权值
2、新增汇点 sink ,并将原图中所有边权为负的顶点引一条边指向汇点 sink ,边权为顶点的权值的绝对值
3、对于原图中所有边,把边的权值变为 无穷大 ,加入网络中
对建好的网络求一次最大流,设所有权值为正的顶点的权值和为 max_vlaue,设网络的最大流是 max_flow ,那么,最大权闭合图的权值就是:max_value-max_flow ,跑了一次最大流后,残留网络形成了一个割集,将网络中的点分为了两份:包含 source 的和包含 sink 的,其中包含 source 的那一个顶点集,在除去了源点source 后,就是原图的最大权闭合图
例题:网络流24题中的太空飞行计划问题
#include <iostream> #include <cstdio> #include <cstring> #include <cmath> #include <algorithm> #include <vector> #include <string> #include <queue> #include <stack> #include <map> #include <set> #include <list> #define INT_INF 0x3fffffff #define LL_INF 0x3fffffffffffffff #define EPS 1e-12 #define MOD 1000000007 #define PI 3.141592653579798 #define N 110 #define E 5000 using namespace std; typedef long long LL; typedef unsigned long long ULL; typedef double DB; struct Edge { int en,cap,flow,next; } edge[E]; int head[N] , tot , now[N]; int source,sink,tot_num; int pre[N] , dis[N] , gap[N]; char s[N]; bool vs[N]; void add_edge(int st,int en,int cap) { edge[tot].en=en; edge[tot].cap=cap; edge[tot].flow=0; edge[tot].next=head[st]; head[st]=tot++; edge[tot].en=st; edge[tot].cap=0; edge[tot].flow=0; edge[tot].next=head[en]; head[en]=tot++; } void augment(int flow) { for(int i=source; i!=sink; i=edge[now[i]].en) { edge[now[i]].flow+=flow; edge[now[i]^1].flow-=flow; } } int sap() { memset(dis,0,sizeof(dis)); memset(gap,0,sizeof(gap)); memset(pre,-1,sizeof(pre)); for(int i=0; i<tot_num; i++) now[i]=head[i]; gap[0]=tot_num; int point=source,flow=0,min_flow=INT_INF; while(dis[source]<tot_num) { bool fg=false; for(int i=now[point]; i!=-1; i=edge[i].next) if(edge[i].cap-edge[i].flow>0 && dis[point]==dis[edge[i].en]+1) { min_flow=min(min_flow,edge[i].cap-edge[i].flow); now[point]=i; pre[edge[i].en]=point; point=edge[i].en; if(point==sink) { flow+=min_flow; augment(min_flow); point=source; min_flow=INT_INF; } fg=true; break; } if(fg) continue; if(--gap[dis[point]]==0) break; int Min=tot_num; for(int i=head[point]; i!=-1; i=edge[i].next) if(edge[i].cap-edge[i].flow>0 && Min>dis[edge[i].en]) { Min=dis[edge[i].en]; now[point]=i; } gap[dis[point]=Min+1]++; if(point!=source) point=pre[point]; } return flow; } int build(int n,int m) { memset(head,-1,sizeof(head)); tot=0; source=0; sink=n+m+1; tot_num=n+m+2; int a,ans=0; for(int i=1; i<=n; i++) { gets(s); int pos=0 , len=strlen(s) , cnt=0; while(pos<len) { int res=0; while(s[pos]==' ' && pos<len) pos++; if(pos==len) break; while(s[pos]>='0' && s[pos]<='9') res=res*10+(s[pos++]-'0'); if(++cnt==1) { add_edge(source,i+m,res); ans+=res; } else add_edge(i+m,res,INT_INF); } } for(int i=1; i<=m; i++) { scanf("%d",&a); add_edge(i,sink,a); } return ans; } void dfs(int u) { vs[u]=1; for(int i=head[u]; i!=-1; i=edge[i].next) if(edge[i].cap-edge[i].flow>0 && !vs[edge[i].en]) dfs(edge[i].en); } vector<int> ans1; vector<int> ans2; int main() { int n,m; while(scanf("%d%d",&n,&m)!=EOF) { gets(s); int tot=build(n,m); int ans=tot-sap(); memset(vs,0,sizeof(vs)); dfs(source); ans1.clear(); ans2.clear(); for(int i=1;i<=n+m;i++) if(vs[i]) { if(i>m) ans1.push_back(i-m); else ans2.push_back(i); } int len1=(int)ans1.size(); int len2=(int)ans2.size(); for(int i=0; i<len1; i++) { printf("%d",ans1[i]); if(i==len1-1) printf("\n"); else printf(" "); } for(int i=0; i<len2; i++) { printf("%d",ans2[i]); if(i==len2-1) printf("\n"); else printf(" "); } printf("%d\n",ans); } return 0; }