W 教授正在为国家航天中心计划一系列的太空飞行。每次太空飞行可进行一系列商业性实验而获取利润。现已肯定了一个可供选择的实验集合 E={E1,E2,…,Em},和进行这些实验须要使用的所有仪器的集合I={I1, I2,…In}。 实验 Ej须要用到的仪器是I的子集。配置仪器Ik的费用为ck美圆。实验Ej的赞助商已赞成为该实验结果支付pj美圆。W教授的任务是找出一个有效算法, 肯定在一次太空飞行中要进行哪些实验并所以而配置哪些仪器才能使太空飞行的净收益最大。这里净收益是指进行实验所得到的所有收入与配置仪器的所有费用的差额。node
【编程任务】:ios
对于给定的实验和仪器配置状况,编程找出净收益最大的试验计划。git
输入文件的第1行有 2 个正整数 m和n(0 < m,n <= 100)。m是实验数,n是仪器数。接下来的 m 行,每行是一个实验的有关数据。第一个数赞助商赞成支付该实验的费用;接着是该实验须要用到的若干仪器的编号,以一个0做为行的结束标记。最后一行的 n个数是配置每一个仪器的费用。算法
输出文件的第1行是实验编号;第2行是仪器编号;最后一行是净收益。编程
2 3网络
10 1 2 0spa
25 2 3 03d
5 6 7code
1 2blog
1 2 3
17
周末,小Hi和小Ho所在的班级决定举行一些班级建设活动。
根据周内的调查结果,小Hi和小Ho一共列出了N项不一样的活动(编号1..N),第i项活动可以产生a[i]的活跃值。
班级一共有M名学生(编号1..M),邀请编号为i的同窗来参加班级建设活动须要消耗b[i]的活跃值。
每项活动都须要某些学生在场才可以进行,若其中有任意一个学生没有被邀请,这项活动就没有办法进行。
班级建设的活跃值是活动产生的总活跃值减去邀请学生所花费的活跃值。
小Hi和小Ho须要选择进行哪些活动,来保证班级建设的活跃值尽量大。
好比有3项活动,4名学生:
第1项活动产生5的活跃值,须要编号为一、2的学生才能进行;
第2项活动产生10的活跃值,须要编号为三、4的学生才能进行;
第3项活动产生8的活跃值,须要编号为二、三、4的学生才能进行。
编号为1到4的学生须要消耗的活跃值分别为六、三、五、4。
假设举办活动集合为{1},须要邀请的学生集合为{1,2},则获得的班级活跃值为5-9 = -4。
假设举办活动集合为{2},须要邀请的学生集合为{3,4},则获得的班级活跃值为10-9 = 1。
假设举办活动集合为{2,3},须要邀请的学生集合为{2,3,4},则获得的班级活跃值为18-12 = 6。
假设举办活动集合为{1,2,3},须要邀请的学生集合为{1,2,3,4},则获得的班级活跃值为23-18 = 5。
小Hi和小Ho老是但愿班级活跃值越大越好,所以在这个例子中,他们会选择举行活动2和活动3。
小Ho:此次的问题好像仍是很麻烦的样子啊。
小Hi:没错,小Ho你有什么想法么?
小Ho:我么?我能想到只有枚举啦。由于每一项活动都只有举行和不举行两种状态,所以我直接用O(2^N)的枚举,再对选出来的状况进行计算。最后选出最大的方案。
小Hi:这很明显会超过期间限制吧。
小Ho:我知道啊,那有什么好的方法么?
小Hi:固然有啊,此次咱们须要解决的是闭合子图问题。
小Ho:这个闭合子图是啥?
小Hi:所谓闭合子图就是给定一个有向图,从中选择一些点组成一个点集V。对于V中任意一个点,其后续节点都仍然在V中。好比:
在这个图中有8个闭合子图:∅,{3},{4},{2,4},{3,4},{1,3,4},{2,3,4},{1,2,3,4}
小Ho:闭合子图我懂了,可是这跟咱们此次的问题有啥关系呢?
小Hi:咱们先把此次的问题转化为2分图。将N个活动看做A部,将M个学生看做B部。若第i个活动须要第j个学生,就连一条从A[i]到B[j]的有向边。好比对于例子:
假如选择A[1],则咱们须要同时选择B[1],B[2]。那么选择什么活动和其须要的学生,是否是就恰好对应了这个图中的一个闭合子图呢?
小Ho:你这么一说好像还真是。若是把活跃值算做权值,A部的节点包含有正的权值,B部的节点是负的权值。那么咱们要求的也就是一个权值最大的闭合子图了?
小Hi:没错,咱们要求解的正是最大权闭合子图。它的求解方法是使用网络流,所以咱们须要将这个图再进一步转化为网络流图。
对于通常的图来讲:首先创建源点s和汇点t,将源点s与全部权值为正的点相连,容量为权值;将全部权值为负的点与汇点t相连,容量为权值的绝对值;权值为0的点不作处理;同时将原来的边容量设置为无穷大。举个例子:
对于咱们题目中的例子来讲,其转化的网络流图为:
上图中黑边表示容量无穷大的边。
小Ho:转化模型这一步看上去不是太难,而后呢?
小Hi:先说说结论吧,最大权闭合子图的权值等于全部正权点之和减去最小割。
接下来来证实这个结论,首先咱们要证实两个引理:
\1. 最小割必定是简单割
简单割指得是:割(S,T)中每一条割边都与s或者t关联,这样的割叫作简单割。
由于在图中将全部与s相连的点放入割集就能够获得一个割,且这个割不为正无穷。而最小割必定小于等于这个割,因此最小割必定不包含无穷大的边。所以最小割必定一个简单割。
\2. 简单割必定和一个闭合子图对应
闭合子图V和源点s构成S集,其他点和汇点t构成T集。
首先证实闭合子图是简单割:若闭合子图对应的割(S,T)不是简单割,则存在一条边(u,v),u∈S,v∈T,且c(u,v)=∞。说明u的后续节点v不在S中,产生矛盾。
接着证实简单割是闭合子图:对于V中任意一个点u,u∈S。u的任意一条出边c(u,v)=∞,不会在简单割的割边集中,所以v不属于T,v∈S。因此V的全部点均在S中,所以S-s是闭合子图。
由上面两个引理能够知道,最小割也对应了一个闭合子图,接下来证实最小割就是最大权的闭合子图。
首先有割的容量C(S,T)=T中全部正权点的权值之和+S中全部负权点的权值绝对值之和。
闭合子图的权值W=S中全部正权点的权值之和-S中全部负权点的权值绝对值之和。
则有C(S,T)+W=T中全部正权点的权值之和+S中全部正权点的权值之和=全部正权点的权值之和。
因此W=全部正权点的权值之和-C(S,T)
因为全部正权点的权值之和是一个定值,那么割的容量越小,W也就越大。所以当C(S,T)取最小割时,W也就达到了最大权。
小Ho:我懂了,由于最小割也对应了一个闭合子图,所以它是能够被取得的,W也才可以到达最大权值。
小Hi:没错,这就是前面两条引理的做用。
小Ho:那么最小割的求解就仍是用最大流来完成好了!
小Hi:嗯,那就交给你了。
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<cstdio> #include<iomanip> #include<cstdlib> #define MAXN 0x7fffffff typedef long long LL; const int N=205; using namespace std; inline int Getint(){register int x=0,f=1;register char ch=getchar();while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}return x*f;} int n,m,S,T,num; struct node{int next,to,pair,flow;}g[N*N]; int h[N],cnt; void AddEdge(int x,int y,int z){ g[++cnt].to=y,g[cnt].next=h[x],h[x]=cnt,g[cnt].flow=z,g[cnt].pair=cnt+1; g[++cnt].to=x,g[cnt].next=h[y],h[y]=cnt,g[cnt].flow=0,g[cnt].pair=cnt-1; } int ans,GAP[N],dis[N]; int Dfs(int x,int Maxf){ if(x==T||!Maxf)return Maxf; int ret=0; for(int i=h[x];i;i=g[i].next){ int to=g[i].to; if(g[i].flow&&dis[x]==dis[to]+1){ int dlt=Dfs(to,min(g[i].flow,Maxf-ret)); g[i].flow-=dlt; g[g[i].pair].flow+=dlt; ret+=dlt; if(dis[S]==num||ret==Maxf)return ret; } } if(!(--GAP[dis[x]]))dis[S]=num; else GAP[++dis[x]]++; return ret; } int SAP(){ int ans=0; while(dis[S]<num)ans+=Dfs(S,MAXN); return ans; } #include<queue> priority_queue<int,vector<int>,greater<int> >q; bool vis[N]; void Find(int x){ if(x)q.push(x); vis[x]=1; for(int i=h[x];i;i=g[i].next){ int to=g[i].to; if(g[i].flow&&!vis[to])Find(to); } } int sum=0; int main(){ m=Getint(),n=Getint(),S=0,T=n+m+1,num=n+m+2; for(int i=1;i<=m;i++){ int x=Getint();sum+=x; AddEdge(S,i,x); for(int x=Getint();x;x=Getint())AddEdge(i,x+m,MAXN); } for(int i=1;i<=n;i++)AddEdge(i+m,T,Getint()); int ret=SAP();Find(S); while(q.top()<=m)cout<<q.top()<<' ',q.pop();cout<<'\n'; while(!q.empty())cout<<q.top()-m<<' ',q.pop();cout<<'\n'; cout<<sum-ret; return 0; }