网络流最大流入门(洛谷P3376)

网络流定义

所谓网络或容量网络指的是一个连通的赋权有向图 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值