引理:最小割最大流定理
搜了一圈没有找到什么是最小割,然后懵了。
首先,什么是割?其实,割 \(=\) 割边 \(=\) 去掉以后使图不连通的边的集合。
然后,容量和最少的割集称为最小割。对于割,有这样一个重要定理:
最小割 \(=\) 最大流。
嗯,最小割就这么多东西。为什么正确?这里给出一种直观的想法。(原 PO):
- 最大流不可能大于最小割,因为最大流所有的水流都一定经过最小割那些割边,流过的水流怎么可能比水管容量还大呢?
- 最大流不可能小于最小割,如果小,那么说明水管容量没有物尽其用,可以继续加大水流.
证毕。
思路
这里首先说一个技巧,无向图的网络流在建边时反向弧直接建成权值为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&∑ 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 的博客 - 洛谷博客