网络流定义
所谓网络或容量网络指的是一个连通的赋权有向图 D= (V、E、C) , 其中V 是该图的顶点集,E是有向边(即弧)集,C是弧上的容量。此外顶点集中包括一个起点和一个终点。
形象点说,可以把每条边比作一个水管,每个水管都有一个流量上界(即最多能通过多少水)与当前流量(即当前流过多少水),而网络流指的就是类似的这样一张图。
最大流问题
定义
给你一个源点(可以把它看做水源)以及一个汇点(可以把它看做水池),求从源点到汇点的最大流量。
求解最大流
求解最大流有两种方法:
EK算法
先介绍几个定义:
残量网络:即边i剩余的流量。
后向弧:即边i的反向边,它的流量上界为0。
增广路:即从源点到汇点的一条路径,满足经过的边残量网络均>0.
EK算法就是不停地找增广路,每找到一条就修改路径上边的流量,直到找不到增广路为止。此时的总量即为答案。
看上去很暴力对不对?
但是因为一般情况找增广路不会太多次就没有增广路了,因此复杂度玄学。
①找增广路
找增广路时用到了BFS,每找到一条残量网络>0的边,就把它所指向的节点加入队列中。如果指向的是汇点就直接返回答案。如果做到队空则说明没有增广路。
int bfs(int now){//增广路,now表示源点
memset(f,false,sizeof(f));
int r=0,w=1;
que[1]=now; f[now]=true;
rem[s]=0x7fffffff;//刚开始把源点的流量改为∞
while (r<w){
int x=que[++r];
for (int i=h[x];~i;i=ed[i].next){
if (!f[ed[i].to]&&ed[i].v>ed[i].flow){//如果当前残量网络大于0
f[ed[i].to]=true;
que[++w]=ed[i].to;//加入队列
fa[ed[i].to].x=x;
fa[ed[i].to].e=i;
rem[ed[i].to]=min(rem[x],ed[i].v-ed[i].flow);//当前最小残量网络
if (ed[i].to==t) return rem[t];
}
}
}
return 0;
}
②修改路径流量
修改路径流量时用到了后向弧。如果经过这条边,就把它的流量加上当前得到的答案,把它的对应边减去当前得到的答案。因为如果经过了后向弧则说明它往回走了,此时应减小流量。
void change(int remain){//remain表示当前增广出来的答案
int now=t;
while (now!=s){
int e=fa[now].e;
ed[e].flow+=remain;//增加当前边的流量
ed[e^1].flow-=remain;//减少对应边的流量
now=fa[now].x;
}
}
算法模板:
以洛谷P3376为例:
#include<cstdio>
#include<cstring>
#include<algorithm>
#define MAXN 100000
#define MAXM 1000000
using namespace std;
struct edge{
int next,to;
int flow,v;
};
struct father{
int x,e;
};
int n,m,k,s,t;
int h[MAXN+5];
edge ed[2*MAXM+5];
father fa[MAXN+5];
int que[MAXN+5],rem[MAXN+5];
bool f[MAXN+5];
inline char readc(){
static char buf[100000],*l=buf,*r=buf;
if (l==r) r=(l=buf)+fread(buf,1,100000,stdin);
if (l==r) return EOF;
return *l++;
}
inline int _read(){
int num=0; char ch=readc();
while (ch<'0'||ch>'9') ch=readc();
while (ch>='0'&&ch<='9') { num=num*10+ch-48; ch=readc(); }
return num;
}
void addedge(int x,int y,int z){
ed[k].next=h[x]; ed[k].to=y; ed[k].v=z; h[x]=k++;
ed[k].next=h[y]; ed[k].to=x; ed[k].v=0; h[y]=k++;
}
int bfs(int now){
memset(f,false,sizeof(f));
int r=0,w=1;
que[1]=now; f[now]=true;
rem[s]=0x7fffffff;
while (r<w){
int x=que[++r];
for (int i=h[x];~i;i=ed[i].next){
if (!f[ed[i].to]&&ed[i].v>ed[i].flow){
f[ed[i].to]=true;
que[++w]=ed[i].to;
fa[ed[i].to].x=x;
fa[ed[i].to].e=i;
rem[ed[i].to]=min(rem[x],ed[i].v-ed[i].flow);
if (ed[i].to==t) return rem[t];
}
}
}
return 0;
}
void change(int remain){
int now=t;
while (now!=s){
int e=fa[now].e;
ed[e].flow+=remain;
ed[e^1].flow-=remain;
now=fa[now].x;
}
}
int maxflow(){
int ans=0;
while (1){
int sum=bfs(s);
if (!sum) return ans;
ans+=sum;
change(sum);
}
}
int main(){
memset(h,-1,sizeof(h));
n=_read(); m=_read(); s=_read(); t=_read();
for (int i=1;i<=m;i++){
int u=_read(),v=_read(),d=_read();
addedge(u,v,d);
}
printf("%d\n",maxflow());
return 0;
}
Dinic算法
dinic就是在增广路上进行了改进。它运用到了分层图的思想,先BFS进行分层,再DFS增广。每次增广时仅当它指向的节点的层次=该节点的层次+1时进行增广。增广同时修改流量。
①BFS分层
同EK一样,只是多求了一个层次而已。
bool bfs(){
memset(f,false,sizeof(f));
int r=0,w=1; dis[s]=0; f[s]=true; que[1]=s;
while (r<w){
int x=que[++r];
for (int i=h[x];~i;i=ed[i].next)
if (!f[ed[i].to]&&ed[i].v>ed[i].flow){
dis[ed[i].to]=dis[x]+1;
f[ed[i].to]=true;
que[++w]=ed[i].to;
}
}
return f[t];
}
②DFS增广
具体见注释:
int dfs(int x,int rem){//x为当前节点,rem为当前最小残量
if (x==t||rem==0) return rem;//如果已经到汇点了或者残量为0就直接返回
int sum=0;
for (int &i=cop[x];~i;i=ed[i].next)//直接从上次做过的地方做
if (dis[ed[i].to]==dis[x]+1){//分层图
int p=dfs(ed[i].to,min(ed[i].v-ed[i].flow,rem));
if (p){//如果找到了
sum+=p; ed[i].flow+=p; ed[i^1].flow-=p; rem-=p;//修改
}
}
return sum;
}
算法模板
仍然是洛谷P3376
#include<cstdio>
#include<cstring>
#include<algorithm>
#define MAXN 100000
#define MAXM 1000000
using namespace std;
struct edge{
int next,to,v,flow;
};
int n,m,s,t,k;
int h[MAXN+5],dis[MAXN+5],cop[MAXN+5],que[MAXN+5];
edge ed[MAXM*2+5];
bool f[MAXN+5];
inline char readc(){
static char buf[100000],*l=buf,*r=buf;
if (l==r) r=(l=buf)+fread(buf,1,100000,stdin);
if (l==r) return EOF; return *l++;
}
inline int _read(){
int num=0; char ch=readc();
while (ch<'0'||ch>'9') ch=readc();
while (ch>='0'&&ch<='9') { num=num*10+ch-48; ch=readc(); }
return num;
}
void addedge(int x,int y,int z){
ed[k].next=h[x]; ed[k].to=y; ed[k].v=z; h[x]=k++;
ed[k].next=h[y]; ed[k].to=x; ed[k].v=0; h[y]=k++;
}
bool bfs(){
memset(f,false,sizeof(f));
int r=0,w=1; dis[s]=0; f[s]=true; que[1]=s;
while (r<w){
int x=que[++r];
for (int i=h[x];~i;i=ed[i].next)
if (!f[ed[i].to]&&ed[i].v>ed[i].flow){
dis[ed[i].to]=dis[x]+1;
f[ed[i].to]=true;
que[++w]=ed[i].to;
}
}
return f[t];
}
int dfs(int x,int rem){
if (x==t||rem==0) return rem;
int sum=0;
for (int &i=cop[x];~i;i=ed[i].next)
if (dis[ed[i].to]==dis[x]+1){
int p=dfs(ed[i].to,min(ed[i].v-ed[i].flow,rem));
if (p){
sum+=p; ed[i].flow+=p; ed[i^1].flow-=p; rem-=p;
}
}
return sum;
}
int maxflow(){
int ans=0;
while (bfs()){
memcpy(cop,h,sizeof(cop));
ans+=dfs(s,0x7fffffff);
}
return ans;
}
int main(){
memset(h,-1,sizeof(h));
n=_read(); m=_read(); s=_read(); t=_read();
for (int i=1;i<=m;i++){
int u=_read(),v=_read(),d=_read();
addedge(u,v,d);
}
printf("%d\n",maxflow());
return 0;
}