本来想找一道网络流的基础题练练手,结果一上来就这么恐怖。。。。不玩了[○・`Д´・ ○]。但看了题解后,挣扎了一番,还是搞懂了,下面就蒟蒻浅薄的知识浅浅的谈一谈,应该会很容易看懂
知识锦囊
在做这道题之前你需要掌握的一些术语,整理如下
闭合图:在一个图中,选择一些点作为一个集合,满足这些点的出边的终端依然属于这个集合,就称这个点的集合为一个闭合图
举例:如图所示,这个图里的闭合图有
{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;
}