2024 年春节集训 _ 第四课 - 网络流
\(EK\) 算法
考虑 \(EK\) 算法求解最大流。
每次找一条最小剩余流量 \(d>0\) 的 \(s\rightsquigarrow t\) 的路径。然后对之流下 \(d\) 的水。这个操作我们称之为增广,所找到的这条路径叫做增广路。
一直增广到不存在任何增广路为止。
发现这样的贪心策略有时是错误的。
考虑反悔。连一条反边,反边最开始容量为 \(0\),在每次正边流过的时候反边剩余流量增加。增广时可以考虑反边的流。但是会有很多反边的起点是没办法到达的。
所以增广依然会结束。
这样连反边然后不断增广的算法被称作 \(EK\) 算法。
最大流最小割定理
定义 \(s-t\) 割为将 \(s,t\) 不连通所要切断的边边权之和。
最大流 \(=\) 最小割。
证明略。
程式
#include <bits/stdc++.h>
#define ll long long
#define fr(x) for (int e = fir[x]; e; e = nxt[e])
using namespace std;
const int N = 1e5 + 10, inf = 1e9 + 7;
int m, n, s, t, top;
int fir[N], nxt[N], c[N], to[N];
int prv[N];
inline void add(int u, int v, int w) {
nxt[++top] = fir[u], fir[u] = top, to[top] = v, c[top] = w;
nxt[++top] = fir[v], fir[v] = top, to[top] = u, c[top] = 0;
}
bool vis[N];
inline int up(int x) {
if (x & 1)
return x + 1;
else
return x - 1;
}
inline bool bfs(void) {
queue<int> q;
q.push(s);
int v;
memset(vis, 0, sizeof vis);
memset(prv, 0, sizeof prv);
while (!q.empty()) {
int u = q.front();
q.pop();
fr(u) {
if (c[e] > 0 && !vis[v = to[e]]) {
prv[v] = e, vis[v] = 1;
q.push(v);
if (v == t)
return true;
}
}
}
return false;
}
inline int augment(void) {
int d = inf;
for (int u = t, e; u != s; u = to[up(e)]) e = prv[u], d = min(d, c[e]);
for (int u = t, e; u != s; u = to[up(e)]) e = prv[u], c[e] -= d, c[up(e)] += d;
return d;
}
int ans;
inline void maxflow(void) {
ans = 0;
while (bfs()) ans += augment();
return;
}
int main() {
scanf("%d%d", &m, &n);
s = 1, t = n;
for (int i = 1; i <= m; ++i) {
int o1, o2, o3;
scanf("%d%d%d", &o1, &o2, &o3);
add(o1, o2, o3);
}
maxflow();
printf("%d\n", ans);
}
但是这样做的复杂度是 \(\mathcal{O(nm^2)}\) 的,不算优秀。
所以你需要一些优化剪枝。也就是 \(Dinic.\)
\(Dinic\) 算法
分层图优化
考虑到 \(EK\) 算法需要每次增广都跑一次 \(bfs.\) 太过麻烦。所以使用分层图优化。
设 \(d[u]\) 表示 \(s\rightsquigarrow t\) 的最短距离。
在后面的 \(dfs\) 中仅需 \(dfs d[v]=d[u]+1\) 的 \(v\) 就好了。不为什么。
当前弧优化
考虑到之前 \(dfs\) 满的边就没必要再 \(dfs\) 了。记 \(cur[i]\) 为 \(i\) 接下来要 \(dfs\) 的边。
在一次 \(dfs\) 过后下次就没必要再 \(dfs\) 这里了。
依然是 \(bfs \rightarrow dfs \rightarrow bfs \cdots\) 这样进行的。
多路增广
一次 \(dfs\) 你需要解决很多个增广路。
所以在 \(dfs\) 一个点的同时增广多路。
程式
#include <bits/stdc++.h>
#define int long long
#define fr(x) for(int e=fir[x];e;e=nxt[e])
using namespace std;
const int N=5005,inf=0x3f3f3f3f;
int fir[N],nxt[N<<2],c[N<<2],to[N<<2],now[N],d[N];
int tot=1,n,m,s,t;
bool vis[N];
inline void add(int u,int v,int w){
nxt[++tot]=fir[u], fir[u]=tot, c[tot]=w, to[tot]=v;
nxt[++tot]=fir[v], fir[v]=tot, c[tot]=0, to[tot]=u;
}
bool bfs(){
queue<int> q; q.push(s);
for(int i=1;i<=n;++i) d[i]=-1;
d[s]=0;
while(!q.empty()){
int u=q.front(); q.pop();
now[u]=fir[u];
fr(u){
int v=to[e];
if(c[e]==0||d[v]!=-1) continue;
d[v]=d[u]+1, q.push(v);
}
}
return (bool)(~d[t]);
}
int dfs(int u,int in){
int sum=0;
if(u==t) return in;
for(int e=now[u];e;e=nxt[e]){
int v=to[e];
now[u]=e;
if(d[u]+1!=d[v]||!c[e]) continue;
int r=dfs(v,min(in-sum,c[e]));
sum+=r, c[e]-=r, c[e^1]+=r;
if(in==sum) break;
}
return sum;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin>>n>>m;
s=1, t=n;
for(int i=1;i<=m;++i){
int x,y,z;
cin>>x>>y>>z, add(x,y,z);
}
int ans=0;
while(bfs()) ans+=dfs(s,inf);
return cout<<ans<<"\n",0;
}
例题
P1345 奶牛的电信 这个其实很有意思啊,考虑最小割点集。
需要把每个点拆分,这样就能实现所谓“打碎点”了。
为了保证这个点在被打碎后无效,把这个点 \(u\) 的所有入边插到 \(u\) 上,剩余的插到 \(copy(u)\) 上。
然后因为不能打碎源点汇点,所以源点放在出点的 \(copy(s)\) 上,但是 \(t\) 原封不动。给个主函数。
注意 \(n\) 的大小,不然初始化有可能会漏一些东西。
程式
#include <bits/stdc++.h>
#define int long long
#define fr(x) for(int e=fir[x];e;e=nxt[e])
using namespace std;
const int N=50005,inf=0x3f3f3f3f;
int fir[N],nxt[N<<2],c[N<<2],to[N<<2],now[N],d[N];
int tot=1,n,m,s,t;
bool vis[N];
inline void add(int u,int v,int w){
nxt[++tot]=fir[u], fir[u]=tot, c[tot]=w, to[tot]=v;
nxt[++tot]=fir[v], fir[v]=tot, c[tot]=0, to[tot]=u;
}
bool bfs(){
queue<int> q; q.push(s);
for(int i=1;i<=n;++i) d[i]=-1;
d[s]=0;
while(!q.empty()){
int u=q.front(); q.pop();
// cout<<u<<" "<<d[u]<<"\n";
now[u]=fir[u];
fr(u){
int v=to[e];
// cout<<"try "<<v<<" c d "<<c[e]<<" "<<d[v]<<"\n";
if(c[e]==0||d[v]!=-1) continue;
d[v]=d[u]+1, q.push(v);
}
}
return (bool)(~d[t]);
}
int dfs(int u,int in){
int sum=0;
if(u==t) return in;
for(int e=now[u];e;e=nxt[e]){
int v=to[e];
now[u]=e;
if(d[u]+1!=d[v]||!c[e]) continue;
int r=dfs(v,min(in-sum,c[e]));
sum+=r, c[e]-=r, c[e^1]+=r;
if(in==sum) break;
}
return sum;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin>>n>>m>>s>>t, s+=n;
for(int i=1;i<=n;++i) add(i,i+n,1);
for(int i=1;i<=m;++i){
int x,y;
cin>>x>>y, add(x+n,y,inf),add(y+n,x,inf);
}
int ans=0;
n*=2;
while(bfs()) ans+=dfs(s,inf);
return cout<<ans<<"\n",0;
}

浙公网安备 33010602011771号