LOJ #6001. 「网络流 24 题」太空飞行计划

orz

这题是最大权闭合子图的模板题。

闭合子图:所有点的后继都在图内。

模型
有一些正权点,负权点。
每个正权点有一些后继。
你需要求一个最大权闭合子图。

做法: a n s = ans= 正权点值和 - 最小割

证明:

在这里插入图片描述

给二分图加入源点,汇点。
正权点与S的连边的容量为其权值。
负权点与T的连边的容量为其权值的相反数。
正权点和其后继的连边的容量为 i n f inf .
跑完网络流后,那么残余网络上与S相连的部分的点权和即为答案。

解释:由于中间的边为 i n f inf ,所以一定不会被割掉,跟S相连的部分的 |负权点的权值和|\le 正权点的权值和 ,也就是获得收益。(割边都对应负权点)

而和T相连的部分就是不会得到收益。(割边都对应正权点)。

有一个更方便的做法是: a n s = ans= 正权点值和 - 最小割。就是后一部分不计入答案。

扫描二维码关注公众号,回复: 11284973 查看本文章
#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;
}

猜你喜欢

转载自blog.csdn.net/qq_42886072/article/details/106259358