bzoj 4500 差分约束系统

题意:n*m的网格,初始为0.有两种操作:(1)把某一行全+1 (2)把某一列全+1 ,对其中m个格子有要求:(xi,yi)格子里的数是ci。问通过两种操作是否可以同时满足m个格子的要求,可以的话输出‘Yes’否则输出‘No’

(xi,yi)格子里的数是ci 对操作的限制其实是 这个格子必须被修改i次 即 第i行和第j列必须一共被修改ci次

我们用xi表示在满足要求时,第i行的操作次数(是个未知数);

          yi表示在满足要求时,第i列的操作次数(是个未知数)

那么,我们通过m个限制就得到了m个等式,我们的问题就变成了这个等式组是否有解

一个naive的想法就是把这m个等式变成2*m个等价的不等式,然后利用差分约束系统来建图判断解的存在性

但是,会发现,我们的得到的等式的形式是 xi+yj=c 的形式,如果利用差分约束系统的思想,xi和yi作为点,c作为边权的话,我们是无法将它变成差分约束系统的标准形式的(-yj 是什么鬼)

那么我们考虑究竟什么样的限制是会导致无解的,什么样的形式才可以变化成标准的差分约束系统

对于第xi行,它和多个列有不同的限制,而当这些列的限制冲突的时候就无解,对于列同理。

同时,对于我们得到的等式组,如果等号的左边是减法的话,我们就可以很轻易的变成标准形式

所以,其实真正会影响解的存在性的限制并不是第i行和第j列,而是和第i行同时有限制的列之间的关系、和第j列同时有限制的行之间的关系,如果这些关系能够同时满足就有解

所以对于行相同的 xi+yj=c1 ①   xi+yk=c2 ②

我们用①-②,得到yj-yk=c1-c2 即 yj-yk<=c1-c2 且 yk-yj<=c2-c1

对于列相同的同理

这样我们就建图判断是否有负环即可

var
        t,n,m,k,l       :longint;
        flag            :boolean;
        i,j             :longint;
        last,dis        :array[0..2010] of longint;
        pre,other,len   :array[0..2000010] of longint;
        vis             :array[0..2010] of boolean;
        x,y,z           :array[0..1010] of longint;

procedure connect(x,y,z:longint);
begin
   inc(l);
   pre[l]:=last[x];
   last[x]:=l;
   other[l]:=y;
   len[l]:=z;
end;

procedure dfs(x:longint);
var
        p,q:longint;
begin
   if flag then exit;
   vis[x]:=true;
   q:=last[x];
   while (q<>0) do
   begin
      if flag then exit;
      p:=other[q];
      if (dis[p]>dis[x]+len[q]) then
      begin
         dis[p]:=dis[x]+len[q];
         if vis[p] then
         begin
            flag:=true;
            exit;
         end else dfs(p);
      end;
      q:=pre[q];
   end;
   vis[x]:=false; 
end;

procedure check;
begin
   fillchar(dis,sizeof(dis),0);
   fillchar(vis,sizeof(vis),false);
   flag:=false;
   for i:=1 to n+m do
   begin
      dfs(i);
      if flag then exit;
   end;
end;

begin
   read(t);
   while (t>0) do
   begin
      dec(t);
      read(n,m,k);
      l:=0; flag:=false;
      fillchar(last,sizeof(last),0);
      for i:=1 to k do read(x[i],y[i],z[i]);
      for i:=1 to k do
        for j:=i+1 to k do
        begin
           if flag then break;
           if (x[i]=x[j]) and (y[i]=y[j]) and (z[i]<>z[j]) then
           begin
              flag:=true; break;
           end;
           if (x[i]=x[j]) and (y[i]<>y[j]) then
           begin
              connect(n+y[i],n+y[j],z[j]-z[i]);
              connect(n+y[j],n+y[i],z[i]-z[j]);
           end else
           if (x[i]<>x[j]) and (y[i]=y[j]) then
           begin
              connect(x[i],x[j],z[j]-z[i]);
              connect(x[j],x[i],z[i]-z[j]);
           end;
        end;
      if not flag then check;
      if not flag then writeln('Yes') else writeln('No');
   end;
end.
——by Eirlys



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值