[网络流]最大流
网络流最大流最小割
就是一道点割。
先说边割
边割比较常见。
最大流
最大流等于最小割,我懒得证。
求最大流的思路就是每次尝试找一条从源点到汇点的通路,然后找到这条路上残余流量最小的流量,答案加上这个流量,这条通路上每条边的残余流量减去这个值,反向边加上这个值。
关于反向边,实际上是一个反悔机制。反向流多少,表示正向已经流了多少。也就是说,如果我们从反向边流了一些流量,就相当于从这条边退回了一部分流量。
点割
所谓点割,就是被割掉的不再是边,而是点。思路是将点转化成边。
如上题:将每个点拆成\(i\)和\(i+n\)两个点,中间连一条流量为\(1\)的边,将这条边割掉,就相当于割掉这个点。
详见代码。
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
using namespace std;
long long read(){
long long x = 0; int f = 0; char c = getchar();
while(c < '0' || c > '9') f |= c == '-', c = getchar();
while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
return f ? -x:x;
}
const int INF = 2147483647;
int n, m, s, t;
struct szh{
int nxt, to, w;
}a[4004];
int head[203],cnt=1;
void add(int x, int y, int w){
a[++cnt].nxt = head[x], a[cnt].to = y, head[x] = cnt, a[cnt].w = w;
}
int dis[203];
bool bfs(){
memset(dis, 0, sizeof dis);
queue<int> q;
q.push(s), dis[s] = 1;
while(!q.empty()){
int u = q.front();q.pop();
for (int i = head[u], v; v = a[i].to, i; i = a[i].nxt)
if(a[i].w && !dis[v]) q.push(v), dis[v] = dis[u] + 1;
}
return dis[t];
}
int fir[103];
int dfs(int u, int flow){
if(u == t || !flow) return flow;
int ans = 0;
for (int &i = fir[u], v; v = a[i].to, i; i = a[i].nxt)
//弧优化
if(a[i].w && dis[v] == dis[u] + 1){
int x = dfs(v, min(flow, a[i].w));
a[i].w -= x, a[i^1].w += x, ans += x; //更新残余流量
if(!(flow-=x)) break;
}
return ans;
}
void dinic(){
int ans = 0;
while(bfs()){
for (int i = 1; i <= (n << 1); ++i) fir[i] = head[i];
//为dfs中取址操作做铺垫
ans += dfs(s,INF);
}
printf("%d", ans);
}
int main(){
n = read(), m = read(), s = read(), t = read();
s += n; //源点不能删掉
for (int i = 1; i <= n; ++i) add(i, i + n, 1), add(i + n, i, 0);
//拆点
for (int i = 1; i <= m; ++i){
int x = read(), y = read();
add(x + n, y, INF), add(y, x + n, 0); //电脑之间不会断,所以连INF
add(y + n, x, INF), add(x, y + n, 0);
}
dinic(); //模板
return 0;
}