网络流
性质
对于任意一个时刻,设f(u,v)实际流量,则整个图G的流网络满足3个性质:
容量限制:对任意u,v∈V,f(u,v)≤c(u,v)。
反对称性:对任意u,v∈V,f(u,v) = -f(v,u)。从u到v的流量一定是从v到u的流量的相反值。
流守恒性:对任意u,若u不为S或T,一定有∑f(u,v)=0,(u,v)∈E。即u到相邻节点的流量之和为0,因为流入u的流量和u点流出的流量相等,u点本身不会"制造"和"消耗"流量。
开端
最大流
从源点到汇点的最大流量
主要思路 :
从源点dfs还有剩余容量的点,如果搜到汇点,证明还有增广路,更新最大流,更新容量,建立反向边(先建立正反两条边,正边为容量,反边为零,dfs时减小正边流量,增加反向边容量)
优化:
dinic使用bfs,将该边的剩余容量给到下一个边,以达到同时搜多条边
为防止搜回来,先使用bfs进行分层,每次搜索只能搜到下一层
代码实现
bool bfs(){//bfs分层,防止多条路同时搜索时搜回来
memset(dep,0,sizeof(dep));//初始化
q.push(root);
dep[root]=1;//第几层
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(e[i].val&&!dep[v]){//还有容量并且是下一层
dep[v]=dep[u]+1;
q.push(e[i].to);
}
}
}
return dep[t];//判断是否有增广路,如果搜不到dep[t]说明没有增广路
}
lt dfs(int u,lt in){
if(u==t){
return in;//如果搜到了汇点,就返回贡献值
}
lt out=0;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(in<=0){
break;
}
if(e[i].val/*还有容量*/&&dep[v]==dep[u]+1/*是下一层*/){
lt res = dfs(v,min(in,e[i].val));
e[i].val-=res;//减小该边容量
e[i^1].val += res;//反向建边;
in-=res;//该边的剩余容量减少,将剩余容量给到下一条边
out+=res;
}
}
if(out==0) {
dep[u] = 0;//连不到t,没有继续搜下去的必要了
return 0;
}
return out;//返回增加的流量
}
再优化:
当前弧优化:再每遍dfs时,记录cur数组,将当前点的head记录为当前的边(因为每一条边如果已经流满就没有在去搜的必要了)再次dfs到当前点时直接从cur记录的位置开始,每一次bfs时初始化cur数组,将其设置为节点的head值
代码实现
完整代码:#include<bits/stdc++.h>
#define inf 0x3f3f3f3f3f3f3f3f
#define lt long long
using namespace std;
const int N=205,M=5005;
lt n,m,root,t;
lt ens;
lt tot=1;
lt head[N];
int cur[N];
struct edge{
int nxt,to;
lt val/*剩余容量*/;
}e[M*4];
void ade(int x,int y,lt z){
e[++tot].to=y;
e[tot].nxt=head[x];
e[tot].val=z;
head[x]=tot;
}
lt dep[N];
queue<int> q;
bool bfs(){//bfs分层,防止多条路同时搜索时搜回来
memset(dep,0,sizeof(dep));//初始化
q.push(root);
dep[root]=1;
cur[root]=head[root];
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(e[i].val&&!dep[v]){//还有容量并且是下一层
cur[v]=head[v];
dep[v]=dep[u]+1;
q.push(e[i].to);
}
}
}
return dep[t];
}
lt dfs(int u,lt in){
if(u==t){
return in;//如果搜到了汇点,就返回贡献值
}
lt out=0;
for(int i=cur[u];i;i=e[i].nxt){
int v=e[i].to;
if(in<=0){
break;
}
if(e[i].val/*还有容量*/&&dep[v]==dep[u]+1/*是下一层*/){
cur[u]=i;
lt res = dfs(v,min(in,e[i].val));
e[i].val-=res;
e[i^1].val += res;//反向建边;
in-=res;
out+=res;
}
}
if(out==0) {
dep[u] = 0;//连不到t,没有继续搜下去的必要了
return 0;
}
return out;
}
int main(){
//freopen("in.txt", "r", stdin);
//freopen("out.txt", "w", stdout);
scanf("%lld%lld%lld%lld",&n,&m,&root,&t);
for(int i=1;i<=m;i++){
int u,v;
lt val;
scanf("%d%d%lld",&u,&v,&val);
ade(u,v,val);
ade(v,u,0);//反向建边,但容量为0,待搜到后再增加容量
}
while(bfs()){//仍有增广路
ens+=dfs(root,inf);
}
printf("%lld\n",ens);
return 0;
}
时间复杂度 (n^2* m);
发展
最小费用最大流
顾名思义就是在维护最大流的同时,使得花费最小。通过使用spfa来实现,用EK的思想,每次搜到一条价格最低的增广路,直到不能再找到增广路为止。
具体实现:
用spfa走有剩余容量的边,对于每一个节点都维护新增流量(该次spfa中到他时的最大流量)、花费、进入的边、由哪个点到。每次spfa中,对于花费比他+到子节点的边的花费大的子节点用他来更新。每次看是否更新到汇点,如果没有更新到
(缓慢更新中……)