【ICPC-Beijing 2006】狼抓兔子

引理:最小割最大流定理

搜了一圈没有找到什么是最小割,然后懵了。

首先,什么是割?其实,割 \(=\) 割边 \(=\) 去掉以后使图不连通的边的集合。

然后,容量和最少的割集称为最小割。对于割,有这样一个重要定理:

最小割 \(=\) 最大流。

嗯,最小割就这么多东西。为什么正确?这里给出一种直观的想法。(原 PO):

  1. 最大流不可能大于最小割,因为最大流所有的水流都一定经过最小割那些割边,流过的水流怎么可能比水管容量还大呢?
  2. 最大流不可能小于最小割,如果小,那么说明水管容量没有物尽其用,可以继续加大水流.

证毕。

思路

这里首先说一个技巧,无向图的网络流在建边时反向弧直接建成权值为v的边即可,因为这样的边一开始就是可以增广的。

题意是求一个无向图的最小割,然后我们运用最小割-最大流定理,可以转化成求最大流的问题。不过朴素的 Dinic 是会 \(\color{#052242}{\text{TLE}}\) 的,这里提供一种优化方法:

我们知道,假定在一次 Dinic 过程中,发现不能再进行增广了,那么就相当于向下的这条路是废的。因此,我们可以直接把这条路堵上,然后就可以过了。

代码

#include<bits/stdc++.h>
#define LL long long
const LL N=8e6+5,inf=1e9;
using namespace std;
struct edge {
    LL to,nxt,w;
} e[N];
LL s,t,cnt=1,head[N],d[N];
void add(LL u,LL v,LL w) {
    e[++cnt].to=v,e[cnt].w=w,e[cnt].nxt=head[u],head[u]=cnt;
}
bool bfs() {
    queue<LL> q;
    q.push(s),memset(d,0,sizeof(d)),d[s]=1;
    while(q.size()) {
        LL x=q.front();
        q.pop();
        for(LL i=head[x]; i; i=e[i].nxt) {
            LL v=e[i].to;
            if(e[i].w>0&&!d[v]) {
                q.push(v),d[v]=d[x]+1;
                if(v==t) {
                    return 1;
                }
            }
        }
    }
    return 0;
}
LL dfs(LL x,LL sum) {
    if(x==t) {
        return sum;
    }
    LL res=0;
    for(int i=head[x]; i&&sum; i=e[i].nxt) {
        int v=e[i].to;
        if(!e[i].w||d[v]!=d[x]+1) {
            continue;
        }
        int k=dfs(v,min(sum,e[i].w));
        if(!k) {
            d[v]=-1;
        }
        e[i].w-=k,e[i^1].w+=k,res+=k,sum-=k;
    }
    return res;
}
LL n,m;
int turn(int x,int y) {
    return m*(x-1)+y;
}
int main() {
    scanf("%lld %lld",&n,&m),s=1,t=turn(n,m);
    for(int i=1; i<=n; i++) {
        for(int j=1,w; j<m; j++) {
            scanf("%d",&w),add(turn(i,j),turn(i,j+1),w);
            add(turn(i,j+1),turn(i,j),w);
        }
    }
    for(int i=1; i<n; i++) {
        for(int j=1,w; j<=m; j++) {
            scanf("%d",&w),add(turn(i,j),turn(i+1,j),w);
            add(turn(i+1,j),turn(i,j),w);
        }
    }
    for(int i=1; i<n; i++) {
        for(int j=1,w; j<m; j++) {
            scanf("%d",&w),add(turn(i,j),turn(i+1,j+1),w);
            add(turn(i+1,j+1),turn(i,j),w);
        }
    }
    LL ans=0;
    while(bfs()) {
        ans+=dfs(s,inf);
    }
    printf("%lld",ans);
    return 0;
}

参考资料:网络流学习笔记(三) 最小割 平面图转对偶图 - LiRewriter 的博客 - 洛谷博客

【ICPC-Beijing 2006】狼抓兔子

上一篇:pychram 中 Terminal 中 git log 中文乱码解决办法


下一篇:SQL语法 - AND & OR 运算符