首先诚挚感谢BYvoid大神博客里提供的好题和数据
此为线性规划与网络流24题的第二题。
自己没想出来,看byvoid写的大致思路的时候我是一脸“还有这种操作”的表情Orz。
«问题描述:
W 教授正在为国家航天中心计划一系列的太空飞行。每次太空飞行可进行一系列商业
性实验而获取利润。现已确定了一个可供选择的实验集合E={E1,E2,…,Em},和进行这
些实验需要使用的全部仪器的集合I={I1,I2,…In}。实验Ej需要用到的仪器是I的子集RjÍI。
配置仪器Ik的费用为ck美元。实验Ej的赞助商已同意为该实验结果支付pj美元。W教授的
任务是找出一个有效算法,确定在一次太空飞行中要进行哪些实验并因此而配置哪些仪器才
能使太空飞行的净收益最大。这里净收益是指进行实验所获得的全部收入与配置仪器的全部
费用的差额。
«编程任务:
对于给定的实验和仪器配置情况,编程找出净收益最大的试验计划。
«数据输入:
由文件input.txt提供输入数据。文件第1行有2 个正整数m和n。m是实验数,n是仪
器数。接下来的m 行,每行是一个实验的有关数据。第一个数赞助商同意支付该实验的费
用;接着是该实验需要用到的若干仪器的编号。最后一行的n个数是配置每个仪器的费用。
«结果输出:
程序运行结束时,将最佳实验方案输出到文件output.txt 中。第1 行是实验编号;第2
行是仪器编号;最后一行是净收益。
直接贴BYvoid大神留下的思路好了。
【问题分析】
最大权闭合图问题,可以转化成最小割问题,进而用最大流解决。
【建模方法】
把每个实验看作二分图X集合中的顶点,每个设备看作二分图Y集合中的顶点,增加源S和汇T。
1、从S向每个Xi连接一条容量为该点收入的有向边。
2、从Yi向T连接一条容量为该点支出的有向边。
3、如果一个实验i需要设备j,连接一条从Xi到Yj容量为无穷大的有向边。
统计出所有实验的收入只和Total,求网络最大流Maxflow,最大收益就是Total-Maxflow。对应的解就是最小割划分出的S集合中的点,也就是最后一次增广找到阻塞流时能从S访问到的顶点。
【建模分析】
定义一个割划分出的S集合为一个解,那么割集的容量之和就是(未被选的A集合中的顶点的权值 + 被选的B集合中的顶点的权值),记为Cut。A集合中所有顶点的权值之和记为Total,那么Total - Cut就是(被选的A集合中的顶点的权值 - 被选的B集合中的顶点的权值),即为我们的目标函数,记为A。要想最大化目标函数A,就要尽可能使Cut小,Total是固定值,所以目标函数A取得最大值的时候,Cut最小,即为最小割。
该问题的一般模型为最大权闭合图,相关讨论见《最小割模型在信息学竞赛中的应用》作者胡伯涛。
//诚挚感谢byvoid大神提供的题目和数据 #include<cstdio> #include<cstring> #include<cstdlib> #include<queue> #include<iostream> #define rep(i,a,b) for(int i=a;i<=b;++i) using namespace std; const int MAXN=10010; int tmp,m,n,S,T,Ans; int tot=0,f=0,ans=0,Maxflow=0; int h[MAXN]; int pointer[MAXN]; int a[MAXN],path[MAXN]; const int INF=~0U>>1; struct edge{ int next,to; int cap,op; //op指向反向边 cap为容量 f为流量 int f; }edge[MAXN*100]; void addedge(int a,int b,int c) //a到b容量为c { edge[tot].to=b; edge[tot].next=pointer[a]; edge[tot].cap=c; pointer[a]=tot; edge[tot].op=tot^1; edge[tot].f=0; tot++; edge[tot].to=a; //建立b到a的反向边 edge[tot].next=pointer[b]; edge[tot].cap=0; pointer[b]=tot; edge[tot].op=tot^1; edge[tot].f=0; tot++; } void Input() { memset(pointer,-1,sizeof(pointer)); scanf("%d%d",&m,&n); T=m+n+1;S=0; int a,c; rep(i,1,m) { scanf("%d",&c); addedge(S,i,c); ans+=c; for (;;) { do c=getchar(); while(c==' '); ungetc(c,stdin); if (c==10 || c==13) break; scanf("%d",&a); addedge(i,a+m,INF); } } rep(j,1,n) { scanf("%d",&c); addedge(j+m,T,c); } } bool Dinic_restruct() //bfs建立层次图 { queue <int>q; memset(h,-1,sizeof(h)); h[S]=0;q.push(S); while(!q.empty()) { int u=q.front();q.pop(); for(int i=pointer[u];i!=-1;i=edge[i].next) { int v=edge[i].to; if(h[v]==-1&&edge[i].cap-edge[i].f) { h[v]=h[u]+1; q.push(v); // printf("v=%d T=%d u=%d ",v,T,u); if(v==T) return true; } } } return false; } void Dinic_augment() //dfs找最短增光路 { int i,j,f,Stop=1; a[Stop]=S; while(Stop) { i=a[Stop]; // printf("augment i=%d ",i); if(i!=T) //没到T就走一步,dfs { int v; for(j=pointer[i];j!=-1;j=edge[j].next) { v=edge[j].to; if(h[v]==h[i]+1&&edge[j].cap-edge[j].f) break; } if(j!=-1) { a[++Stop]=v; path[Stop]=j; } else //回退 { Stop--; h[i]=-1; // printf("-1 augment i=%d ",i); } } else //找到了一条完整的路径 { f=INF; for(i=Stop;i>=2;i--) { int &t=path[i]; if(edge[t].cap-edge[t].f<f) f=edge[t].cap-edge[t].f; } Maxflow+=f; for(int i=Stop;i>=2;i--) { int &t=path[i]; edge[t].f+=f; edge[t^1].f-=f; //反向边 if(edge[t].cap-edge[t].f==0) Stop=i-1; } } } } void Dinic() { while(Dinic_restruct()) Dinic_augment(); } void Pout() //高度为-1代表在最后一次建图时从S连接不到这个点,赚钱的实验组必然有至少一个实验到S的路径是未堵塞的 { //从而这个实验组包含的实验和所需仪器都与S是连通的 rep(i,1,m) if(h[i]!=-1) printf("%d ",i); printf(" "); rep(i,m+1,m+n) if(h[i]!=-1) printf("%d ",i-m); printf(" %d ",ans-Maxflow); } int main() { // freopen("shut2.in","r",stdin); Input(); Dinic(); Pout(); return 0; }