0815-网络流之最大流-最大权闭合图-太空飞行计划

本来想找一道网络流的基础题练练手,结果一上来就这么恐怖。。。。不玩了[○・`Д´・ ○]。但看了题解后,挣扎了一番,还是搞懂了,下面就蒟蒻浅薄的知识浅浅的谈一谈,应该会很容易看懂

传送门

知识锦囊

在做这道题之前你需要掌握的一些术语,整理如下

闭合图:在一个图中,选择一些点作为一个集合,满足这些点的出边的终端依然属于这个集合,就称这个点的集合为一个闭合图

举例:如图所示,这个图里的闭合图有

     {5}、{2,5}、{4,5}

     {2,4,5}、{3,4,5}

     {2,3,4,5}、{1,2,4,5}

     {1,2,3,4,5}

最大权闭合图:在所有的闭合图中,点权之和最大的那个闭合图,被称之为最大权闭合图

举例:上图中的最大权闭合图(点权为圈圈上面的数字)为{3,4,5}

然后我会告诉你这个知识锦囊一点用都没有,哈哈,说着玩的,只是这种方法理解起来太困难了,我选择放弃,给大家贴一个地址,有兴趣可以自己去看看https://www.cnblogs.com/wuyiqi/archive/2012/03/12/2391960.html

分析

下面讲一个通俗易懂的版本,先别问为什么(虽然我还不知道是怎么想到这样做的,但至少我能理解这个算法)

我们虚拟一个源点-0号节点,一个汇点- n + m + 1

然后在源点和实验之间连接一条值为赞助费的边

在实验和其对应的仪器之间连接一条正无穷大的边

在仪器和汇点之间连接一条值为该仪器使用费用的边

最后在这个图上跑一遍最大流,用总赞助费(tot)减去最大流(maxflow)即为最大的净收益

接着我们来感性理解一下这样做的正确性

Case 1 :这个实验是亏本的,意味着赞助费小于仪器的费用,那么最后流到汇点的就是赞助费的大小。当 tot -maxflow 的时候,这个实验的赞助费就会被减为0,相当于不做这个实验

Case 2:这个实验不亏本,那么最终流到汇点的就是仪器的成本了,用 tot - maxflow 就得到了净收益

Case 3:这个实验 A 本身亏本,但它所需要的仪器恰好别的实验 B 可以提供,那这个实验 A 就可以操作并且盈利。这时候,对于实验 B 就是Case 2 的情况,那么实验 A 的赞助费就没有被减掉,仍然在 tot 里,符合我们操作这个实验并且会盈利的条件

再说如何输出选择的实验和相应的仪器,这个很简单,在你最后一次 bfs 的时候就已经得到了答案。因为最后一次 bfs 是找不到增广路的,那就意味着我们已经找到了最大流(也就是最小割),那么这次的 bfs 所访问到的点(也就是 lev 不为-1 的点),就是 最小割割出来的两个集合中 包含0号节点的那个集合,而我们又可以证明这个集合便是该图的一个闭合图,所以我们用到的实验和仪器就都在这个集合里了

证明的话大概是这样的:

由于实验和仪器之间连接的是 INF ,所以最小割(小于 INF )是不可能包含连接实验和仪器之间的边的(也就是说没有实验和仪器是被割在两个集合中的),那么割出来的集合就是一个闭合图咯,实验指向仪器,仪器指向实验

证毕(更准确的证明可以参见上面我给的链接)

 

代码

我是用的 dinic 来找最大流,方法多样,自行选择,反正我只会这一种

#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<vector>
#include<cstdlib>
#include<algorithm>
#define N 2000000
#define M 1000
#define inf 1e9
using namespace std;
int nxt[N],head[M],to[N],cap[N],from[N],tot=1;
int n,m;
void add(int x,int y,int z){
	nxt[++tot]=head[x];head[x]=tot;to[tot]=y;from[tot]=x;cap[tot]=z;
	nxt[++tot]=head[y];head[y]=tot;to[tot]=x;from[tot]=y;cap[tot]=0;	
}
char ch[M],val[M];
int st,end,lev[M],cur[M],used[M];
bool bfs(){
	int que[M],qn=1,i,j,k;
	for(i=0;i<=end;++i) lev[i]=-1,cur[i]=head[i];
	que[1]=0;lev[0]=0;
	for(i=1;i<=qn;++i){
		int u=que[i];
		for(j=head[u];j;j=nxt[j]){
			int v=to[j];
			if(cap[j]>0&&lev[v]==-1){
				lev[v]=lev[u]+1;
				que[++qn]=v;
				if(v==end) return true;
			}
		}
	}
	return false;
}
int dinic(int u,int flow){
	if(u==end) return flow;
	int res=0,delta;
	for(int &e=cur[u];e;e=nxt[e]){
		int v=to[e];
		if(lev[v]>lev[u]&&cap[e]>0){
			delta=dinic(v,min(flow-res,cap[e]));
			if(delta){ 
			//	printf("u=%d v=%d delta=%d\n",u,v,delta);
			//	system("pause");
				res+=delta;
				cap[e]-=delta;cap[e^1]+=delta;
				if(res==flow) break;
			}
		}
	}
	if(res<=flow) lev[u]=-1;
	return res; 
}
int maxflow(){
	int ans=0;
	while(bfs()) ans+=dinic(st,inf);
	return ans;
}
int main(){
	scanf("%d%d",&m,&n);
	int i,j,k,total=0;
	st=0,end=n+m+1;
	for(i=1;i<=m;++i){
		int f,sum=0;
		scanf("%d",&f);
		total+=f;
		add(st,i,f);
		gets(ch);
		int len=strlen(ch);
		ch[len]=' ';
		for(j=0;j<=len;++j){
			if(ch[j]>='0'&&ch[j]<='9')	sum=sum*10+ch[j]-48;
			else {
				if(sum) 	add(i,m+sum,inf);
				sum=0;
			}
		}
	}
	for(i=1;i<=n;++i){
		scanf("%d",&k);
		add(m+i,end,k);//不可以连 i 到 end 
	}
	int cut=maxflow();
	for(i=1;i<=m;++i)
		if(lev[i]!=-1) printf("%d ",i);
	printf("\n");
	for(i=1;i<=n;++i)
		if(lev[i+m]!=-1) printf("%d ",i);
	printf("\n");
	printf("%d",total-cut);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_42557561/article/details/81674017