[二分答案][网络流验证][BZOJ 3232]圈地游戏

这几天都在准备会考没时间写题,这道题算是这几天唯一的成果了....

会考求A...

下面进入正题。

Description

DZY家的后院有一块地,由N行M列的方格组成,格子内种的菜有一定的价值,并且每一条单位长度的格线有一定的费用。
DZY喜欢在地里散步。他总是从任意一个格点出发,沿着格线行走直到回到出发点,且在行走途中不允许与已走过的路线有任何相交或触碰(出发点除外)。记这条封闭路线内部的格子总价值为V,路线上的费用总和为C,DZY想知道V/C的最大值是多少。

Analysis
题目要求使一个比值最大,那么我们便可以二分答案g,求MAX(V-gC)。
先将所有格子的价值加起来,然后便可以转化成最大权闭合图的模型,每个点向汇点T连边,容量为该格子的价值,表示不选该点就会失去该价值。源点S向每个在边界上的点连边,容量为该点边界(一条或两条边)的费用和*g,表示选了该点必须要支付这么多代价。然后每两个有公共边的格子连两条有向边,容量为公共边费用*g,表示若一个在S集一个在T集的话,必须要支付的代价。然后求一遍最小割。若最小割<价值和,那么该答案可行,l=mid;若最小割=价值和,那么不可行,r=mid。
还有精度不要太大,刚开始我定了1e-8结果RE了= =|||
上代码:
const lim=1e-6;
var
  n,m,i,j,edge,vs,vt,num,sum:longint;
  a,b,c:array[0..51,0..51] of longint;
  e:array[1..800000,1..2] of longint;
  ee:array[1..800000] of extended;
  fr,d,vd:array[0..2510] of longint;
  l,r,mid,u,ans,tmp:extended;
procedure add(x,y:longint;c:extended);
begin
  inc(edge); e[edge,1]:=y; e[edge,2]:=fr[x]; ee[edge]:=c; fr[x]:=edge;
end;
function dcmp(d:extended):longint;
begin
  if abs(d)<lim then exit(0);
  if d<0 then exit(-1) else exit(1);
end;
function get(x,y:longint):longint;
begin
  get:=(x-1)*m+y;
  if (get<1) or (get>n*m) then get:=vt-1;
end;
function max(a,b:extended):extended;
begin
  if a>b then exit(a) else exit(b);
end;
function min(a,b:extended):extended;
begin
  if a<b then exit(a) else exit(b);
end;
function dfs(u:longint;flow:extended):extended;
var
  k:longint;
  tmp:extended;
begin
  if u=vt then exit(flow);
  dfs:=0;
  k:=fr[u];
  while k<>0 do begin
    if (dcmp(ee[k])=1) and (d[u]=d[e[k,1]]+1) then begin
      tmp:=dfs(e[k,1],min(ee[k],flow-dfs));
      dfs:=dfs+tmp;
      ee[k]:=ee[k]-tmp;
      if odd(k) then ee[k+1]:=ee[k+1]+tmp else ee[k-1]:=ee[k-1]+tmp;
      if dcmp(dfs-flow)=0 then exit;
    end;
    k:=e[k,2];
  end;
  dec(vd[d[u]]);
  if vd[d[u]]=0 then d[vs]:=num;
  inc(d[u]);
  inc(vd[d[u]]);
end;
begin
  readln(n,m);
  for i:=1 to n do
    for j:=1 to m do begin
      read(a[i,j]);
      sum:=sum+a[i,j];
    end;
  for i:=1 to n+1 do
    for j:=1 to m do
      read(b[i,j]);
  for i:=1 to n do
    for j:=1 to m+1 do
      read(c[i,j]);
  vs:=0; vt:=n*m+1; num:=vt+1;
  l:=0; r:=1000000;
  repeat
    mid:=(l+r)/2;
    fillchar(d,sizeof(d),0);
    fillchar(vd,sizeof(vd),0);
    fillchar(fr,sizeof(fr),0);
    vd[0]:=num;
    for i:=1 to n do
      for j:=1 to m do begin
        add(vs,get(i,j),a[i,j]);
        add(get(i,j),vs,0);
      end;
    for i:=1 to n do
      for j:=1 to m do begin
        tmp:=0;
        if i=1 then tmp:=tmp+b[i,j];
        if i=n then tmp:=tmp+b[i+1,j];
        if j=1 then tmp:=tmp+c[i,j];
        if j=m then tmp:=tmp+c[i,j+1];
        add(get(i,j),vt,tmp*mid);
        add(vt,get(i,j),0);
      end;
    for i:=1 to n-1 do
      for j:=1 to m do begin
        add(get(i,j),get(i+1,j),b[i+1,j]*mid);
        add(get(i+1,j),get(i,j),b[i+1,j]*mid);
      end;
    for i:=1 to n do
      for j:=1 to m-1 do begin
        add(get(i,j),get(i,j+1),c[i,j+1]*mid);
        add(get(i,j+1),get(i,j),c[i,j+1]*mid);
      end;
    ans:=0;
    while d[vs]<num do ans:=ans+dfs(vs,maxlongint);
    tmp:=sum-ans;
    if dcmp(tmp)>0 then l:=mid else r:=mid;
  until r-l<=lim;
  writeln(l:0:3);
end.


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值