并不会从零开始讲网络流 , 并且其中很多是个人理解.
\(①\) \(:\) 最大流 \(\cdot\) \(DK\)
每次去找 一条 (注意是一条) 路增广 , 再去更新.
\(vis\) 保证每次每个点只找到一次 , 也防止双向边成环卡死.
一定不能用 \(flow\) 单个变量去记录最小流 , 要用数组 \(flow\) 去更新 , 因为在一条路的路程中会产生最小流 , 但这条路不一定走得通.
关于反向边的建立 , 我认为就是让能流的残余流量流完
很显然答案是 : 198.
但若我们走的不是 "显然" 的路 , 走 \(1 \rightarrow 2 \rightarrow 3 \rightarrow 4\) , 则会变成 :
这一次我们只能走 \(1 \rightarrow 3 \rightarrow 2 \rightarrow 4\) , 则会变成 :
三个图总体来看发现 : \(2 - 3\) 反向边的建立让我们两条显然的路都经过了流量 \(1\) .
#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;
const int N = 201;
const int M = 5001;
const int inf = 2147483647;
long long ans;
int n,m,s,t,cnt = 1;
int head[N],vis[N],pre[N],flow[N];
struct bian{
int to,v,next;
}len[M<<1];
void add(int from,int to,int v){
len[++cnt].v = v;
len[cnt].to = to;
len[cnt].next = head[from];
head[from] = cnt;
}
bool bfs(){
queue<int> Q;
flow[s] = inf;
memset(vis,0,sizeof(vis));
Q.push(s) , vis[s] = 1;
while(Q.size()){
int now = Q.front();Q.pop();
for(int k=head[now];k;k=len[k].next){
int to = len[k].to , v = len[k].v;
if(!vis[to]&&v){
//flow = min(flow,v);
flow[to] = min(v,flow[now]);
pre[to] = k;
Q.push(to),vis[to] = 1;
if(to == t) return true;
}
}
}
return false;
}
void updata(){
int x = t;
while(x != s){
int id = pre[x];
len[id].v -= flow[t];
len[id^1].v += flow[t];
x = len[id^1].to;
}
ans += flow[t];
}
int main()
{
scanf("%d%d%d%d",&n,&m,&s,&t);
for(int i=1;i<=m;++i){
int x,y,v;scanf("%d%d%d",&x,&y,&v);
add(x,y,v),add(y,x,0);
}
while(bfs()) updata();
printf("%lld",ans);
return 0;
}
\(②\) \(:\) 最大流 \(\cdot\) \(Dinic\)
分层数组 \(dep\) : 保证能多路增广 , 且防止双向边成环卡死.
如图 : \(1 \rightarrow 2 \rightarrow 4\) , \(1 \rightarrow 3 \rightarrow 4\) , 两条路能在一次 \(dfs\) 中完成增广 , 而不是像 \(DK\) 那次被 \(vis\) 数组限定每次只能选一条.
当前弧优化 \(now\) 数组 : 当一条路进行了增广后 , 我们没必要在同一次 \(dfs\) 中对其再增广 , 不好解释看图吧.
当我们走了 \(1 \rightarrow 2 \rightarrow 4 \rightarrow 6\) , \(1 \rightarrow 2 \rightarrow 4 \rightarrow 5\) 时 ,
下一次就只需走 \(1 \rightarrow 3 \rightarrow 4 \rightarrow 7\) 即可 , 从 \(6\) , \(5\) 走一定会遇到 边权为 \(0\) 边.
注意我们用 &k = now[x]
进行对 \(now\) 数组的实时更新.
小技巧优化 : nowflow < flow
, nowflow += nodeflow
.
结合图与递归就能理解了 :
#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;
const int N = 201;
const int M = 5001;
const int inf = 2147482647;
long long ans;
int n,m,s,t,cnt = 1;
int head[N],now[N],dep[N];
struct bian{
int to,next,v;
}len[M<<1];
void add(int from,int to,int v){
len[++cnt].v = v;
len[cnt].to = to;
len[cnt].next = head[from];
head[from] = cnt;
}
bool bfs(){
queue<int> Q;
memset(dep,0,sizeof(dep));
Q.push(s) , dep[s] = 1;
while(Q.size()){
int pos = Q.front();Q.pop();
for(int k=head[pos];k;k=len[k].next){
int to = len[k].to , v = len[k].v;
if(!dep[to]&&v){
dep[to] = dep[pos] + 1;
Q.push(to);
if(to == t) return true;
}
}
}
return false;
}
int dfs(int x,int flow){
if(x == t) return flow;
int nowflow = 0;
for(int &k=now[x];k&&nowflow<flow;k=len[k].next){
int to = len[k].to , v = len[k].v;
if(dep[to]==dep[x]+1&&v){
int nodeflow = dfs(to,min(v,flow-nowflow));
if(nodeflow > 0){
len[k].v -= nodeflow;
len[k^1].v += nodeflow;
nowflow += nodeflow;
}
}
}
return nowflow;
}
int main()
{
scanf("%d%d%d%d",&n,&m,&s,&t);
for(int i=1;i<=m;++i){
int x,y,v;scanf("%d%d%d",&x,&y,&v);
add(x,y,v),add(y,x,0);
}
while(bfs()){
for(int i=1;i<=n;++i)
now[i] = head[i];
while(int f = dfs(s,inf))
ans += f;
}
printf("%lld",ans);
return 0;
}
\(③\) \(:\) 最小费用最大流 \(\cdot\) \(DK\)
在每次我们找残余网络的基础上 (v != 0
) , 加入对最短路的判断 .
同时注意我们这里的 \(vis\) 数组在数据弹出时要 \(vis = 0\) , 这在最大流 \(\cdot\) \(DK\) 里面是没有的 , 这里利用的是 \(spfa\) 更新最短路的原理 , 当前有效状态更新并非最优 . 利用 dis[to] > dis[x] + cost
不会陷入双向边成环卡死的现象.