[2017雅礼集训]day9 T2 价 最小割/最大权闭合子图

题目大意:有N种药品,每种药品由若干种药材制成,有一个权值Pi,所有药材也恰好为N种,且保证有一种方案使每种药品一 一对应对应一个其使用的药材(即有完美匹配)。求一种选药材的方案,使选的药品数和它们使用的药材数相等,且最小化选出的药品权值和。(1<=N<=300, |Pi|<=1e7)

解法一:建立最小割模型,源点向所有药品连边,容量为INF-pi,药品向制作它的药材连边容量为IIINF(比INF大一级别的),所有药材向汇点连边,容量为INF。求出最小割,再减去源点连出的所有边的容量和即为答案。
为什么呢?首先IIINF保证了药品和药材的边不会被割。我们考虑从所有药品中删去一些药品选剩下的药品,所以要尽可能删掉权值大的那些,即割掉汇点与那些药品连的边,因为是最小割,所以先要对pi先取反。为什么要加INF呢,考虑一个药品没被割,那它对应的那些药材必须全部要割掉,如果割掉的药材比保留的药品多,那么总的最小割含的INF个数就会大于N,然而多一个INF都是致命的,所以这样保证了选的药品数和它们使用的药材数相等。最终计算答案要用所有的INF-pi减去最小割,再取反,所以直接用最小割减去所有INf-pi即可。

解法二:先考虑把药材和药品建成二分图,跑一个最大匹配,那如果要选一个药品,它所对应的 所有药材 所匹配的药品 都必须选,于是我们新建一个图,如果一个药品要选导致其他的一些药品也要选,就从这个点向那些点连边,跑一边最大权闭合子图即可。

解法一代码:

type
  edge=^edgenode;
  edgenode=record
    t,c,f:int64;
    rev,next:edge;
  end;
var
  n,i,j,x,size:longint;
  ans:int64;
  s:array[1..310]of int64;
  p,t:array[1..310]of longint;
  e:array[1..310,1..310]of longint;
  con:array[0..2000]of edge;
  ne,dl:array[0..2000]of longint;
  visit:array[0..2000]of boolean;
const inf=1000000000;
  iinf=1000000000000000000;
procedure ins(x,y,c,f:int64);
var
  p:edge;
begin
  new(p);
  p^.t:=y;
  p^.c:=c;
  p^.f:=0;
  p^.next:=con[x];
  con[x]:=p;
  new(p);
  p^.t:=x;
  p^.c:=0;
  p^.f:=0;
  p^.next:=con[y];
  con[y]:=p;
  con[x]^.rev:=con[y];
  con[y]^.rev:=con[x];
end;
function min(x,y:int64):int64;
begin
  if x>y then exit(y)
  else exit(x);
end;
function bfs:boolean;
var
  p:edge;
  head,tail:longint;
begin
  head:=1;
  tail:=1;
  for i:=0 to size do
  begin
    visit[i]:=false;
    ne[i]:=0;
  end;
  visit[0]:=true;
  dl[1]:=0;
  bfs:=false;
  while head<=tail do
  begin
    if dl[head]=size then bfs:=true;
    p:=con[dl[head]];
    while p<>nil do
    begin
      if (p^.f<p^.c)and(visit[p^.t]=false) then
      begin
        ne[p^.t]:=ne[dl[head]]+1;
        visit[p^.t]:=true;
        inc(tail);
        dl[tail]:=p^.t;
      end;
      p:=p^.next;
    end;
    dl[head]:=0;
    inc(head);
  end;
end;
function dinic(k:longint;z:int64):int64;
var
  p:edge;
  o:int64;
begin
  if k=size then exit(z);
  if visit[k]=true then exit(0);
  p:=con[k];
  dinic:=0;
  while p<>nil do
  begin
    if (ne[p^.t]=ne[k]+1)and(p^.f<p^.c) then
    begin
      o:=dinic(p^.t,min(z-dinic,p^.c-p^.f));
      dinic:=dinic+o;
      inc(p^.f,o);
      if p^.rev<>nil then dec(p^.rev^.f,o);
      if dinic=z then break;
    end;
    p:=p^.next;
  end;
  if dinic=0 then visit[k]:=true;
end;
procedure add;
var
  p:edge;
begin
  p:=con[0];
  while p<>nil do
  begin
    ans:=ans-p^.c;
    p:=p^.next;
  end;
end;
begin

  readln(n);
  for i:=1 to n do
  begin
    read(t[i]);
    for j:=1 to t[i] do
      read(e[i,j]);
  end;
  for i:=1 to n do
    read(p[i]);
  for i:=1 to n do
    for j:=1 to t[i] do
      ins(i,e[i,j]+n,iinf,0);                            
  size:=2*n+1;
  for i:=1 to n do
  begin
    ins(0,i,inf-p[i],0);
    ins(i+n,size,inf,0);
  end;
  ans:=0;
  while bfs do
  begin
    for i:=0 to size do
      visit[i]:=false;
    ans:=ans+dinic(0,iinf);
  end;
  add;
  writeln(ans);


end.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值