网络流24题 太空飞行计划问题___最大权闭合子图

题目大意:

已确定了一个可供选择的实验集合E={E1,E2,…,Em},和实验需要使用的全部仪器的集合I={I1,I2,…In}。实验Ej需要用到的仪器是I的子集RjÍI。配置仪器Ik的费用为ck美元。实验Ej的赞助商已同意为该实验结果支付pj美元。W教授的任务是找出一个有效算法,确定在一次太空飞行中要进行哪些实验并因此而配置哪些仪器才能使太空飞行的净收益最大。这里净收益是指进行实验所获得的全部收入与配置仪器的全部费用的差额。
对于给定的实验和仪器配置情况,编程找出净收益最大的试验计划。

n,m<=50

题解:

增设一个源点s,汇点t,
s向所有实验连接一条它们所能承受的价格的边作为容量
所有仪器向t连一条它们所需要付出的价格的边作为容量
每个实验向所需要的仪器连一条无穷大的边
问题转化为最大权闭合子图问题的模型,进而转化为最小割问题①,
最小割等于最大流,故用网络流解决
需要的仪器跟实验就是最小割划分出的S集合中的点,也就是最后能从S访问到的顶点的集合。
而求最小割的方法是可以从从源点开始走,所有源点能到达的点即为最小割的S集合。

某文章的一段话对①的较为容易理解的解释:
由于我们要求的是最大收益,这个收益应等于∑c[i](实验i被选中) - ∑p[j](仪器j被选中)。为保证受益最大,我们应当尽量想让∑c[i]尽可能大,∑p[j]尽可能小。如果∑c[i]尽可能大,那么,∑c[k](实验k未被选中)就应该尽可能小。也就是说,我们应该选择尽可能小的∑c[k]+ ∑p[j],这样才能使收益最大。而c[k]、p[j]对应的又是刚刚构造的网络流的连接s、t的弧,而且,一旦删去了这些弧,整个网络将是不连通的(此点可用反证法证明)。 于是我们可以顺其自然的想到最小割。
问题就转变成了:求一个网络流中的最小割。
接下来,根据最小割最大流定理,我们就可以代替着求最大流了

鄙人略微理解:
对于这个图而言,你要尽可能使得你所用的花费少的同时使得这个图的源点s无法通向汇点t,
若从 S 引出的边中某一条边没有满流,就说明做这个实验是可以赚钱的,此时你要割取代价最少的边总和,使得花费最小,因为到最后,花费转换一下就可以发现都是由【s–>实验】的边承担
这个时候最小值就是这个图的最小割,根据最小割等于最大流,可求最大流
然后总收益-最少花费=最大收益

代码:

var
          a:Array [0..101,0..101] of longint;
          q:Array [0..10001] of longint;
          rp:Array [0..101] of longint;
          v:array [0..101] of boolean;
          sum,ans,n,m,s,t:longint;

procedure init;
var
          i,j,x:longint;
begin
          readln(m,n);
          sum:=0;
          s:=0;
          t:=n+m+1;
          for i:=1 to m do
            begin
                  read(x);
                  sum:=sum+x;
                  a[s,i]:=x;
                  while not(eoln) do
                       begin
                             read(x);
                             a[i,x+m]:=maxlongint div 4;
                       end;
            end;
          for i:=1 to n do
            begin
                  read(x);
                  a[i+m,t]:=x;
            end;
end;

function min(aa,bb:longint):longint;
begin
          if aa>bb then exit(bb);
          exit(aa);
end;

function bfs():boolean;
var
          i,k,head,tail:longint;
begin
          fillchar(rp,sizeof(rp),255);
          head:=0;
          tail:=1;
          rp[s]:=0;
          q[1]:=s;
          while head<tail do
             begin
                   inc(head);
                   k:=q[head];
                   for i:=s to t do
                     if rp[i]=-1 then
                        if a[k,i]>0 then
                           begin
                                 rp[i]:=rp[k]+1;
                                 if i=t then exit(true);
                                 inc(tail);
                                 q[tail]:=i;
                           end;
             end;
          exit(false);
end;

function dfs(dep,cp:longint):longint;
var
          i,j,k:longint;
begin
          if dep=t then exit(cp);
          k:=0;
          for i:=s to t do
              if a[dep,i]>0 then
                 if rp[dep]+1=rp[i] then
                    begin
                          j:=dfs(i,min(cp-k,a[dep,i]));
                          a[dep,i]:=a[dep,i]-j;
                          a[i,dep]:=a[i,dep]+j;
                          k:=k+j;
                          if k=cp then exit(k);
                    end;
          if k=0 then rp[dep]:=-1;
          exit(k);
end;

procedure dinic;
begin
          ans:=0;
          while bfs() do
                ans:=ans+dfs(s,maxlongint);


end;

procedure find(x:longint);
var
          i:longint;
begin
          v[x]:=true;
          for i:=s to t do
              if a[x,i]>0 then
                 if not(v[i]) then find(i);
end;

procedure print;
var
          i,j:longint;
begin
          find(s);
          for i:=1 to m do
              if v[i] then write(i,' ');
          writeln;
          for i:=m+1 to m+n do
              if v[i] then write(i-m,' ');
          writeln;
          writeln(sum-ans);
end;

begin
          init;
          dinic;
          print;
end.


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值