LOJ #6001. 「網絡流 24 題」太空飛行計劃

orz

這題是最大權閉合子圖的模板題。

閉合子圖:所有點的後繼都在圖內。

模型
有一些正權點,負權點。
每個正權點有一些後繼。
你需要求一個最大權閉合子圖。

做法:ans=ans=正權點值和-最小割

證明:

在這裏插入圖片描述

給二分圖加入源點,匯點。
正權點與S的連邊的容量爲其權值。
負權點與T的連邊的容量爲其權值的相反數。
正權點和其後繼的連邊的容量爲infinf.
跑完網絡流後,那麼殘餘網絡上與S相連的部分的點權和即爲答案。

解釋:由於中間的邊爲infinf,所以一定不會被割掉,跟S相連的部分的|負權點的權值和|\le 正權點的權值和,也就是獲得收益。(割邊都對應負權點)

而和T相連的部分就是不會得到收益。(割邊都對應正權點)。

有一個更方便的做法是:ans=ans=正權點值和-最小割。就是後一部分不計入答案。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=110,inf=2e9;

int n,m,d[N],q[N],ans,st,ed;
struct edge{int y,next,c;}a[N*N]; int len=1,last[N],cur[N],b[N];
void ins(int x,int y,int c) {a[++len]=(edge){y,last[x],c}; last[x]=len;}
void add(int x,int y,int c) {ins(x,y,c); ins(y,x,0);}

bool bfs() {
	memset(d,0,(ed+1)<<2);
	int l,r; q[l=r=1]=st; d[st]=1;
	while(l<=r) {
		int x=q[l++];
		for(int k=last[x],y;k;k=a[k].next) {
			y=a[k].y;
			if(!d[y]&&a[k].c) {
				d[y]=d[x]+1;
				q[++r]=y;
				if(y==ed) return 1;
			}
		}
	}
	return 0;
}

int dfs(int x,int f) {
	if(x==ed)return f;
	int s=0,t;
	for(int k=cur[x],y,z;k&&s<f;k=a[k].next) {
		y=a[k].y; z=a[k].c;
		if(d[y]==d[x]+1&&z>0) {
			s+=(t=dfs(y,min(f-s,z)));
			a[k].c-=t; a[k^1].c+=t; cur[x]=k;
		}
	}
	if(!s) d[x]=0;
	return s;
}

int main() {
	scanf("%d %d",&n,&m); m+=n; st=0; ed=m+1;
	for(int i=1,x;i<=n;i++) {
		scanf("%d",&x); add(st,i,x); ans+=x;
		while(getchar()==' ') scanf("%d",&x),add(i,x+n,inf);
	}
	for(int i=n+1,x;i<=m;i++) scanf("%d",&x),add(i,ed,x);
	while(bfs()) memcpy(cur,last,(ed+1)<<2),ans-=dfs(st,inf);
	for(int i=1;i<=n;i++) if(d[i]) b[++b[0]]=i;
	for(int i=1;i<=b[0];i++) printf("%d%c",b[i]," \n"[i==b[0]]);
	b[0]=0;
	for(int i=n+1;i<=m;i++) if(d[i]) b[++b[0]]=i-n;
	for(int i=1;i<=b[0];i++) printf("%d%c",b[i]," \n"[i==b[0]]);
	printf("%d\n",ans); return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章