网络流总结
dinic算法
算法流程:
1.每条边addE一条反向边
2.需要多次bfs更新图,进行分层
3.多路增广,只走下一层的路径
复杂度:O(n2m)
代码模版:
#include<bits/stdc++.h>
#define LL long long
using namespace std;
int n,m,s,t;
LL ans;
int cnt=1;
vector<int> g[210];
struct edge{
int from,to; LL flow,cap;
}e[40010];
void addE(int u,int v,LL z){
cnt++;
e[cnt].from=u; e[cnt].to=v; e[cnt].cap=z; g[u].push_back(cnt);
}
int vis[201],dep[201];
int bfs(){
queue<int> q;
memset(vis,0,sizeof(vis));
memset(dep,0,sizeof(dep));
q.push(s); vis[s]=1;
while(!q.empty()){
int u=q.front(); q.pop();
for(int i=0; i<g[u].size(); i++){
edge t=e[g[u][i]];
int v=t.to;
if(vis[v] || t.cap == 0) continue;
vis[v]=1; dep[v]=dep[u]+1;
q.push(v);
}
}
return dep[t];
}
LL dinic(int u,LL in){
if(u == t) return in;
LL out=0;
for(int i=0; i<int( g[u].size() ); i++){
int t=g[u][i];
int v=e[t].to;
if(dep[v] == dep[u]+1 && e[t].cap){
LL res=dinic(v,min(in,e[t].cap));
e[t].cap-=res;
e[t^1].cap+=res;
in-=res;
out+=res;
}
}
if(out == 0)
dep[u]=0;
return out;
}
int main(){
// freopen("d.in","r",stdin);
scanf("%d %d %d %d", &n, &m, &s, &t);
for(int i = 1; i <= m; ++i) {
int u, v; LL w;
scanf("%d %d %lld", &u, &v, &w);
addE(u, v, w);
addE(v, u, 0);
}
while(bfs())
ans+=dinic(s,1e18);
cout<<ans;
return 0;
}
ISAP
算法流程:
(1)概述:算法基于这样的一个事实:每次增广之后,任意结点到汇点(在残余网络中)的最短距离都不会减小。这样,我们可以利用d[i]表示结点i到汇点的距离的下界。然后再增广过程当中不断地修改这个下界。增广的时候和Dinic算法类似,只允许沿着d[i]==d[j]+1的弧(i,j)走。
不难证明,d[i]满足两个条件:(1)d[t]=0;(2)对任意的弧(i,j) d[i]≤d[j]+1。因为最坏的情况就是s到t是一条链,此时等号成立。因此,当d[s]≥n时,残余网络中不存在s-t路。
那么与Dinic算法类似,事先逆向bfs,找增广的过程就是沿着“允许弧”(即满足f< c且d[i]==d[j]+1的弧)往前走。(称为“前进”)。如果向前走不动了,那么就要考虑原路返回(称为“撤退”)。此时把d[i]修改为min{d[j]}+1即可。因为要满足d函数的条件(2)。修改后,原来的i值的个数就减少一个,而新i值的个数多一个。在程序中,用num数组来保存所有距离的个数,当把距离值从x修改为y时,num[x]–,num[y]++即可,然后检查num[x]是否为0,如果是0,那么s-t不连通,算法终止。原因显而易见:比如s-t的距离是3,如果距离为2的情况都已经没了,更别提走到距离为1的点了。这就是所谓的“gap优化”。
通过之前的分析,在数据结构方面,该算法只比Dinic算法的数据结构多了两个数组:用于记录父边以便于撤退的数组p,以及标记距离个数的数组num。增广的时候分为两步,第一步逆推求出可改进量a(即残余量的最小值);第二步再逆推一遍,进行增广。主过程中,x走到汇点时增广。
#include<bits/stdc++.h>
using namespace std;
const int N = 10 + 1e4;
const int M = 10 + 1e5 + N;
const int inf = 0x3f3f3f3f;
int n, m, s, t, tot = 1;
int head[M<<1], d[N], h[N], g[N], pre[N];
struct node
{
int to, next, cap, flow;
}e[M<<1];
void add(int a, int b, int c)
{
e[++tot].cap = c; e[tot].flow = 0; e[tot].to = b; e[tot].next = head[a]; head[a] = tot;
e[++tot].cap = 0; e[tot].flow = 0; e[tot].to = a; e[tot].next = head[b]; head[b] = tot;
}
void set_h(int t, int n)
{
queue<int> q;
memset(h, -1, sizeof h);
memset(g, 0, sizeof g);
h[t] = 0;
q.push(t);
while(q.size())
{
int u = q.front(); q.pop();
++g[h[u]];
for(int i = head[u]; ~i; i = e[i].next)
{
int v = e[i].to;
if(h[v] == -1)
{
h[v] = h[u] + 1;
q.push(v);
}
}
}
}
int ISAP(int s, int t, int n)
{
set_h(t, n);
int ans = 0, u = s, d;
while(h[s] < n)
{
int i = head[u];
if(u == s) d = inf;
for(; ~i; i = e[i].next)
{
int v = e[i].to;
if(e[i].cap > e[i].flow && h[u] == h[v]+1)
{
u = v;
pre[v] = i;
d = min(d, e[i].cap-e[i].flow);
if(u == t)
{
while(u != s)
{
int j = pre[u];
e[j].flow += d;
e[j^1].flow -= d;
u = e[j^1].to;
}
ans += d;
d = inf;
}
break;
}
}
if(i == -1)
{
if(--g[h[u]] == 0) break;
int hmin = n-1;
for(int j = head[u]; ~j; j = e[j].next)
if(e[j].cap > e[j].flow)
hmin = min(hmin, h[e[j].to]);
h[u] = hmin+1;
++g[h[u]];
if(u != s) u = e[pre[u]^1].to;//ÍËÒ»²½£¬Ñظ¸±ß·µ»Ø
}
}
return ans;
}
int main()
{
memset(head, -1, sizeof head);
cin>>n>>m>>s>>t;
for(int i = 0; i < m; i++)
{
int a, b, c; cin>>a>>b>>c;
add(a, b, c);
}
cout<<ISAP(s, t, n);
return 0;
}
无源汇
无源汇上下界可行流
问题转换:题目问是否存在满足容量限制的可行流,相当于问新网络里的最大流是否可以达到源点的出边容量之和。
#include<bits/stdc++.h>
using namespace std;
const int N = 2000 + 10;
const int M = (100210 + N) + 10;
const int inf = 0x3f3f3f3f;
int n, m, s, t, tot = 1, tott;
int head[M<<1], d[N], L[M<<1], cur[N], delta[N];
struct Edge
{
int to, next, cap, flow;
}e[M<<1];
void add(int a, int b, int c, int d)
{
e[++tot].cap = d-c; e[tot].flow = 0; e[tot].to = b; e[tot].next = head[a]; head[a] = tot;
L[tot] = c;
e[++tot].cap = 0; e[tot].flow = 0; e[tot].to = a; e[tot].next = head[b]; head[b] = tot;
}
bool bfs(int s, int t)
{
memset(d, 0, sizeof d);
queue<int> q;
d[s] = 1;
cur[s] = head[s];
q.push(s);
while(!q.empty())
{
int u = q.front();
q.pop();
for(int i = head[u]; ~i; i = e[i].next)
{
int v = e[i].to;
if(!d[v] && e[i].cap > e[i].flow)
{
d[v] = d[u]+1;
cur[v] = head[v];
if(v == t) return 1;
q.push(v);
}
}
}
return 0;
}
int dfs(int u, int flow, int t)
{
if(u == t) return flow;
int res = flow;
for(int i = cur[u]; ~i && res; i = e[i].next)
{
cur[u] = i;
int v = e[i].to;
if(d[v] == d[u]+1 && e[i].cap > e[i].flow)
{
int k = dfs(v, min(res, e[i].cap-e[i].flow), t);
if(!k) d[v] = 0;
e[i].flow += k;
e[i^1].flow -= k;
res -= k;
}
}
return flow-res;
}
int dinic()
{
int maxflow = 0, flow;
while(bfs(s, t)) maxflow += dfs(s, inf, t);
return maxflow;
}
int main()
{
memset(head, -1, sizeof head);
cin>>n>>m;
s = 0;
t = n+1;
for(int i = 0; i < m; i++)
{
int a, b, c, d; cin>>a>>b>>c>>d;
add(a, b, c, d);
delta[a] -= c;
delta[b] += c;
}
for(int i = 1; i <= n; i++)
{
if(delta[i] > 0)
{
add(s, i, 0, delta[i]);
tott += delta[i];
}
else if(delta[i] < 0)
add(i, t, 0, -delta[i]);
}
if(dinic() != tott)
{
cout<<"NO";
return 0;
}
// 输出结果为可行流中每条边的流量
cout<<"YES"<<endl;
for(int i = 2; i < m*2+2; i += 2)
cout<<e[i^1].cap-e[i^1].flow+L[i]<<endl;
return 0;
}
有源汇
有源汇上下界可行流
算法详解:为了使源汇点满足流量守恒,所以从汇点t向源点s连一条下界为0上界为无穷大的边,相当于把从源点s流出的流量再流回来。在这样的图中使用无源汇上下界可行流算法求出一个可行流,拆掉从汇点tt到源点ss的边就得到一个有源汇上下界可行流。
#include<bits/stdc++.h>
using namespace std;
const int N = 2000 + 10;
const int M = (100210 + N) + 10;
const int inf = 0x3f3f3f3f;
int n, m, s, t, ss, tt, tot = 1, tott;
int head[M<<1], d[N], L[M<<1], cur[N], delta[N];
struct Edge
{
int to, next, cap, flow;
}e[M<<1];
void add(int a, int b, int c, int d)
{
e[++tot].cap = d-c; e[tot].flow = 0; e[tot].to = b; e[tot].next = head[a]; head[a] = tot;
L[tot] = c;
e[++tot].cap = 0; e[tot].flow = 0; e[tot].to = a; e[tot].next = head[b]; head[b] = tot;
}
bool bfs(int s, int t)
{
memset(d, 0, sizeof d);
queue<int> q;
d[s] = 1;
cur[s] = head[s];
q.push(s);
while(!q.empty())
{
int u = q.front();
q.pop();
for(int i = head[u]; ~i; i = e[i].next)
{
int v = e[i].to;
if(!d[v] && e[i].cap > e[i].flow)
{
d[v] = d[u]+1;
cur[v] = head[v];
if(v == t) return 1;
q.push(v);
}
}
}
return 0;
}
int dfs(int u, int flow, int t)
{
if(u == t) return flow;
int res = flow;
for(int i = cur[u]; ~i && res; i = e[i].next)
{
cur[u] = i;
int v = e[i].to;
if(d[v] == d[u]+1 && e[i].cap > e[i].flow)
{
int k = dfs(v, min(res, e[i].cap-e[i].flow), t);
if(!k) d[v] = 0;
e[i].flow += k;
e[i^1].flow -= k;
res -= k;
}
}
return flow-res;
}
int dinic()
{
int maxflow = 0, flow;
while(bfs(s, t)) maxflow += dfs(s, inf, t);
return maxflow;
}
int main()
{
memset(head, -1, sizeof head);
cin>>n>>m>>ss>>tt;
s = 0;
t = n+1;
for(int i = 0; i < m; i++)
{
int a, b, c, d; cin>>a>>b>>c>>d;
add(a, b, c, d);
delta[a] -= c;
delta[b] += c;
}
for(int i = 1; i <= n; i++)
{
if(delta[i] > 0)
{
add(s, i, 0, delta[i]);
tott += delta[i];
}
else if(delta[i] < 0)
add(i, t, 0, -delta[i]);
}
add(tt, ss, 0, inf);
if(dinic() != tott)
{
cout<<"NO";
return 0;
}
cout<<"YES"<<endl;
cout<<e[tot].cap-e[tot].flow<<endl;
e[tot].flow = e[tot].cap;
return 0;
}
有源汇上下界最大流
算法详解:先使用有源汇上下界可行流算法算出一个可行流,再在这个残余网络上跑一遍ss到tt的最大流。最终的最大流流量 = 可行流流量(即t到s的无穷边上跑出的流量) + 新增广出的ss到tt的流量。
#include<bits/stdc++.h>
using namespace std;
const int N = 2000 + 10;
const int M = (100210 + N) + 10;
const int inf = 0x3f3f3f3f;
int n, m, s, t, ss, tt, tot = 1 , tott;
int head[M<<1], d[N], cur[M<<1], delta[N];
struct Edge
{
int to, next, cap, flow;
}e[M<<1];
void add(int a, int b, int c, int d)
{
e[++tot].cap = d-c; e[tot].flow = 0; e[tot].to = b; e[tot].next = head[a]; head[a] = tot;
e[++tot].cap = 0; e[tot].flow = 0; e[tot].to = a; e[tot].next = head[b]; head[b] = tot;
}
bool bfs(int s, int t)
{
memset(d, 0, sizeof d);
d[s] = 1;
cur[s] = head[s];
queue<int> q;
q.push(s);
while(!q.empty())
{
int u = q.front();
q.pop();
for(int i = head[u]; ~i; i = e[i].next)
{
int v = e[i].to;
if(!d[v] && e[i].cap > e[i].flow)
{
d[v] = d[u]+1;
cur[v] = head[v];
if(v == t) return 1;
q.push(v);
}
}
}
return 0;
}
int dfs(int u, int lim, int t)
{
if(u == t) return lim;
int res = lim;
for(int i = cur[u]; ~i && res; i = e[i].next)
{
cur[u] = i;
int v = e[i].to;
if(d[v] == d[u]+1 && e[i].cap > e[i].flow)
{
int k = dfs(v, min(res, e[i].cap-e[i].flow), t);
if(!k) d[v] = 0;
e[i].flow += k;
e[i^1].flow -= k;
res -= k;
}
}
return lim-res;
}
int dinic()
{
int maxflow = 0;
while(bfs(s, t)) maxflow += dfs(s, inf, t);
return maxflow;
}
int main()
{
memset(head, -1, sizeof head);
cin>>n>>m>>ss>>tt;
s = 0;
t = n+1;
for(int i = 0; i < m; i++)
{
int a, b, c, d; cin>>a>>b>>c>>d;
add(a, b, c, d);
delta[a] -= c;
delta[b] += c;
}
for(int i = 1; i <= n; i++)
{
if(delta[i] > 0)
{
add(s, i, 0, delta[i]);
tott += delta[i];
}
if(delta[i] < 0)
add(i, t, 0, -delta[i]);
}
add(tt, ss, 0, inf);
if(dinic() != tott)
{
cout<<"No Solution";
return 0;
}
s = ss; t = tt;
// 先加可行流流量
int ans = e[tot].cap-e[tot].flow;
e[tot].flow = 0; e[tot].cap = 0;
e[tot-1].flow = 0; e[tot-1].cap = 0;
// 再加新增广为最大流的e(s->t)的流量
ans += dinic();
cout<<ans<<endl;
return 0;
}
有源汇上下界最小流
算法详解:先使用有源汇上下界可行流算法算出一个可行流,再在这个残余网络上
跑一遍tt到ss的最大流(反向边的流量增加等价于正向边的的流量减少。因此
在残余网络上找出tt到ss的流就相当于减小了ss到tt的流)。最终的最小流流量
= 可行流流量 - tt到ss的最大流的流量。
多源汇
多源汇最大流
算法详解:新建超级源点S,向所有源点连容量为inf的边,汇点同理。
问题即转化为普通最大流问题
费用流
最小费用最大流
算法一:最小费用路算法
每次spfa找到最短路,然后沿着spfa增流。
复杂度为 O(VE^2)
#include<bits/stdc++.h>
using namespace std;
int n,m,s,t,cnt,ans_flow,ans_cost;
int pre[50100],vis[50100],d[50100],f[50100];
vector<int> g[50100];
struct node{
int to,flow,cap,cost;
}e[100010];
void add(int a,int b,int c,int d)
{
e[cnt].to=b; e[cnt].cap=c; g[a].push_back(cnt); e[cnt].cost=d; cnt++;
e[cnt].to=a; e[cnt].cap=c; e[cnt].flow=c; g[b].push_back(cnt); e[cnt].cost=-d; cnt++;
}
bool spfa()
{
queue<int> q;
memset(vis,0,sizeof(vis));
memset(d,67,sizeof(d)); d[t]=INT_MAX;
memset(f,0,sizeof(f));
q.push(s); vis[s]=1; f[s]=1000000000; d[s]=0;
while(!q.empty())
{
int u=q.front(); q.pop(); vis[u]=0;
for(int i=0; i<g[u].size(); i++)
{
int v=g[u][i];
if(e[v].cap > e[v].flow && d[e[v].to] > d[u]+e[v].cost)
{
d[e[v].to]=d[u]+e[v].cost;
f[e[v].to]=min(e[v].cap-e[v].flow,f[u]);
pre[e[v].to]=v;
if(!vis[e[v].to])
{
q.push(e[v].to); vis[e[v].to]=1;
}
}
}
}
return d[t] != INT_MAX;
}
void mcmf()
{
while(spfa())
{
int tt=t;
while(tt != s)
{
int i=pre[tt];
e[i].flow+=f[t];
e[i^1].flow-=f[t];
tt=e[i^1].to;
}
ans_flow+=f[t];
ans_cost+=d[t]*f[t];
}
}
int main()
{
cin>>n>>m>>s>>t;
for(int i=1; i<=m; i++)
{
int a,b,c,d;
cin>>a>>b>>c>>d;
add(a,b,c,d);
}
mcmf();
cout<<ans_flow<<" "<<ans_cost;
return 0;
}
算法二:消圈算法
用途:判断是否存在更优的解,时间复杂度低于MCMF。