[BZOJ2406]矩阵(二分+有上下界的网络流)

=== ===

这里放传送门

=== ===

题解

这种式子看一眼就头大。。。日= =
可以发现它实际上就是在说要让每一行和每一列的对应元素差值之和的最大值最小。那么可以考虑二分这个最大值然后判定可行性。
如果二分了一个最大值 lim ,这里以某一行为例,式子可以转化成 |jAijjBij|lim 。那么把绝对值符号展开就成了 limjAijjBijlim 。这个式子里 Aij 都是已知项,那么移项一下就可以变成 limjAijjBijlimjAij ,也就是 jAijlimjBijlim+jAij

这个式子又代表了什么呢?可以发现它的意思就是 B 矩阵每一行和每一列的数字和要在某一段范围之内。然后题目中原本还有一个限制就是B矩阵每一个位置的值要在某一段范围之内。考虑简化版本,矩阵每一行每一列的和不能超过某个给定值,别的没什么限制。这样的话网络流的模型是不是会显然一点呢。。

对于这个题因为有一段范围的限制所以要用到上下界网络流。每一行和每一列分别弄一排点,从S向每一行的点连边,每一列的点向T连边,设置上下界为它这一行或者这一列的限制。每一行向每一列所有点连边,上下界为题目中给定的单点限制 [L,R] 。用有上下界有源汇的网络流模型进行判定,如果能跑出可行流就说明有解。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#define inc(x)(x=(x==500)?1:x+1)
#define inf 2000000000
using namespace std;
const int base=1000;
int n,m,a[210][210],p[510],gap[510],d[510],pre[510],cur[510],tot,sum,f[510];
int rs[210],cs[210],S,T,SS,TT,L,R;
struct edge{
    int to,flw,nxt;
}e[200010];
void add(int from,int to,int flow){
    e[tot].to=to;e[tot].flw=flow;
    e[tot].nxt=p[from];p[from]=tot++;
}
void Bfs(){
    int q[510],head,tail;
    for (int i=S;i<=T;i++){
        cur[i]=p[i];d[i]=T+1;
    }
    head=0;tail=1;q[tail]=T;d[T]=0;
    while (head!=tail){
        int u;inc(head);u=q[head];
        for (int i=p[u];i!=-1;i=e[i].nxt)
          if (e[i^1].flw>0&&d[e[i].to]==T+1){
              int v=e[i].to;d[v]=d[u]+1;
              inc(tail);q[tail]=v;  
          }
    }
}
int Increase(){
    int Min=inf;
    for (int i=T;i!=S;i=e[pre[i]^1].to)
      Min=min(Min,e[pre[i]].flw);
    for (int i=T;i!=S;i=e[pre[i]^1].to){
        e[pre[i]].flw-=Min;
        e[pre[i]^1].flw+=Min;
    }
    return Min;
}
int ISAP(){
    int u=S,Flow=0;
    bool flag=false;
    Bfs();
    for (int i=S;i<=T;i++) gap[d[i]]++;
    while (d[S]<=T){
        if (u==T){
            Flow+=Increase();
            u=S;
        }
        flag=false;
        for (int i=p[u];i!=-1;i=e[i].nxt){
            int v=e[i].to;cur[u]=p[i];
            if (e[i].flw>0&&d[u]==d[v]+1){
                pre[v]=i;u=v;flag=true;break;
            }
        }
        if (flag==false){
            int v=T;
            for (int i=p[u];i!=-1;i=e[i].nxt)
              if (e[i].flw>0) v=min(v,d[e[i].to]);
            gap[d[u]]--;cur[u]=p[u];
            if (gap[d[u]]==0) break;
            d[u]=v+1;++gap[d[u]];
            if (u!=S) u=e[pre[u]^1].to;
        }
    }
    return Flow;
}
bool check(int lim){
    memset(p,-1,sizeof(p));
    tot=sum=0;
    memset(f,0,sizeof(f));
    memset(gap,0,sizeof(gap));
    for (int i=1;i<=n;i++){
        int low=rs[i]-lim,up=rs[i]+lim;//计算上界和下界
        low=max(low,0);//如果流量出现负数应该忽略,因为下界至少是0
        f[i]+=low;f[SS]-=low;
        add(SS,i,up-low);add(i,SS,0);
    }
    for (int i=1;i<=m;i++){
        int low=cs[i]-lim,up=cs[i]+lim;
        low=max(low,0);
        f[TT]+=low;f[i+n]-=low;
        add(i+n,TT,up-low);add(TT,i+n,0);
    }
    for (int i=1;i<=n;i++)
      for (int j=1;j<=m;j++){
          f[j+n]+=L;f[i]-=L;
          add(i,j+n,R-L);add(j+n,i,0);
      }
    add(TT,SS,inf);add(SS,TT,0);
    for (int i=S+1;i<T;i++)
      if (f[i]>0){
          add(S,i,f[i]);add(i,S,0);sum+=f[i];
      }else{add(i,T,-f[i]);add(T,i,0);}
    if (ISAP()!=sum) return false;
    return true;
}
int divide(int l,int r){
    int mid;
    while (l!=r){
        mid=(l+r)>>1;
        if (check(mid)) r=mid;
        else l=mid+1;
    }
    return l;
}
int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++)
      for (int j=1;j<=m;j++){
          scanf("%d",&a[i][j]);
          rs[i]+=a[i][j];
          cs[j]+=a[i][j];
      }
    SS=n+m+1;TT=SS+1;
    S=0;T=TT+1;
    scanf("%d%d",&L,&R);
    printf("%d\n",divide(0,250000));
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值