网络流详解

网络流详解


hehe,说是详解,实际上只是一个知识点的不完整的小汇总,可能日后有所补充

首先介绍三个基本的网络流问题:

最大流问题

问题介绍

这是网络流问题的一种,也是最基本的一种,题意是让你求出一个网络流中从源点到汇点所能存在的最大流量。这个问题有许多的算法,下面来简单地谈一谈:

朴素算法

原理

呵呵,真是到哪里都有朴素算法啊。对于一个网络流,我们考虑它的残量网络中的增广路,我们每次用BFS暴力寻找,然后不断增广,最后一定能得到正确的答案,然而它的效率十分地低下,而且它的时间复杂度与最后最大流的大小有关,这样就非常的坑啦,如果我们考虑一个最大流为1,000,000的网络,由于每次我们都是随机寻找增广路,所以我们可以构造出一个这样的情况,使得这样增广每次只能增广1单位的流量,我们便需要至少1,000,000次增广,一定会超时,所以不推荐这种写法

代码留坑待补

ISAP算法

这是我比较常用的一个算法,它的效率肯定是比朴素算法好得多,虽然不是最好,但也能应付大部分网络流题目了

原理

其实在谈论这个算法之前应该先搞一搞Dinic算法的,因为它可以说是Dinic的一种改良,然而我并没写过Dinic,虽然知道原理。ISAP每次的思路是在增广以后不重建层次图(相对于Dinic算法来说),而是继续寻找增广路,直到出现了一个死路的时候,我们对节点进行一个retreat操作,只改变当前进入死路的节点的d值,这样我们能够节省许多的时间(在Dinic中相当于一次BFS后本来还有许多的资源使用空间,却被无情地浪费掉了一样)。ISAP中还有许多的优化可以提高它的效率,就我个人而言,我还是推荐使用ISAP算法

其中,BFS用于求出每个节点到汇点的最小距离,因为我们假设每一条弧的长度均为1,所以我们可以直接跑BFS

增广时还是用那种传统的方法,每次沿着节点的上一条弧进行移动,统计增量并再次移动把这个增量体现到每一条弧中

下面是代码

代码

#include<queue>
#include<cstdio>
#include<vector>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define maxn 20005
#define INF 2000000005
using namespace std;

struct edge{
    int u,v,cap,flow;
    edge(int u,int v,int cap,int flow){
        this->u=u;
        this->v=v;
        this->cap=cap;
        this->flow=flow;    
    }
    edge():u(0),v(0),cap(0),flow(0){}
};

vector<edge> e;
vector<int> geo[maxn];
int n,m,s,t,k=0,d[maxn],num[maxn],p[maxn],cur[maxn];
bool vis[maxn];

void Add_Edge(int u,int v,int cap){
    e.push_back(edge(u,v,cap,0));
    geo[u].push_back(k);
    e.push_back(edge(v,u,0,0));
    geo[v].push_back(k^1);
    k+=2;
    return;
}//加边,注意此时的边为有向边,否则需要增加四条

bool BFS(){
    queue<int> bfs;
    bfs.push(t);
    d[t]=0;
    vis[t]=true;
    while(!bfs.empty()){
        int u=bfs.front();
        bfs.pop();
        for(int i=0;i<geo[u].size();i++){
            edge op=e[geo[u][i]^1];
            if(!vis[op.u]&&op.cap>op.flow){
                vis[op.u]=true;
                d[op.u]=d[u]+1;
                bfs.push(op.u);
            }
        }
    }
    if(!d[s])return false;
    return true;
}//预处理时的BFS

int Augment(){
    int u=t,flow=INF;
    while(u!=s){
        edge op=e[p[u]];
        flow=min(flow,op.cap-op.flow);
        u=op.u;
    }
    u=t;
    while(u!=s){
        e[p[u]].flow+=flow;
        e[p[u]^1].flow-=flow;
        u=e[p[u]].u;
    }
    return flow;
}//标准的增广过程

int Maxflow(){
    int maxflow=0;
    if(!BFS())return maxflow;
    for(int i=1;i<=n;i++){
        num[d[i]]++;//统计对于每个d值有多少个节点对应
    }
    int u=s;
    while(d[s]<n){
        if(u==t){
            maxflow+=Augment();//增广
            u=s;
        }
        bool if_ope=0;
        for(int i=cur[u];i<geo[u].size();i++){//用cur记录当前节点考虑到的弧的编号
            edge op=e[geo[u][i]];
            if(d[op.v]==d[u]-1&&op.cap>op.flow){
                p[op.v]=geo[u][i];
                cur[u]=i;
                if_ope=1;
                u=op.v;
                break;
            }
        }
        if(!if_ope){//开始节点的retreat部分
            int m=n-1;
            for(int i=0;i<geo[u].size();i++){
                edge op=e[geo[u][i]];
                if(op.cap>op.flow){
                    m=min(m,d[op.v]);
                }
            }//寻找它的邻接节点中d值最小的那个
            num[d[u]]--;
            if(num[d[u]]==0)break;//Gap优化,此时可以证明源点与汇点一定不连通
            num[m+1]++;
            d[u]=m+1;
            cur[u]=0;
            if(u!=s)u=e[p[u]].u;
        }
    }
    return maxflow;
}

int main(){
    /*freopen("input.txt","r",stdin);
    //freopen("output.txt","w",stdout);*/
    scanf("%d%d%d%d",&n,&m,&s,&t);
    int x,y,z;
    for(int i=0;i<m;i++){
        scanf("%d%d%d",&x,&y,&z);
        Add_Edge(x,y,z);
    }
    printf("%d",Maxflow());
    return 0;
}

注意上面代码中的细节,它们大多用注释标记出来了,还有就是上面代码保存弧的时候采用了vector,如果效率不允许可以使用前向星

还有一点。。。十分智障的细节,BFS要使用queue而非stack

Dinic算法

暂时先不写

现在,我们来研究下一个问题,那就是。。。啊,不是,还有一个小小的定理,那就是最小割最大流定理

最小割最大流定理

这个定理的意思就是我们在任何一个流量网络中求出来的最小割一定等于这个网络的最大流,这样所有求解最小割的问题均可以使用最大流算法进行解决

最小割定义

最小割就是能将一个网络分成不相交的两部分的一个弧的集合,我们把这些弧的容量的和称为这个割的容量,而我们的目的即为求解一个网络中的一个容量最小的割,称为最小割,而我们可以证明这个最小割的大小等于这个网络的最大流,证明并不复杂,可以从最小割定义以及网络流的性质出发进行证明

最小费用最大流问题

这个问题就变得比较有实际意义了,并且很好的将网络流问题与普通的图论问题结合在了一起。这个问题应用于一种带费用的网络,即每条弧拥有除了上文提到的属性之外还有一个属性:单位流量的费用,我们要做的,是在保证流量最大的情况下,求出这个网络费用的最小值。

我们的思路很简单,因为我们刚才寻找增广路的方法是相对任意的,只要它的速度足够快就可以,而在这个问题中,我们不能再任意寻找一个可行的最大流,因为它们有了一个新的限制,费用最小。所以我们可以考虑顺序性地遍历网络,即这时我们就可以通过建立一张关于费用的图,并在这个图上跑最短路来实现。最短路的算法我们可以使用SPFA,写起来方便跑起来快

下面是代码

代码

//#pragma GCC optimize "O3"
#include<queue>
#include<cstdio>
#include<vector>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<ext/pb_ds/priority_queue.hpp>
#define maxn 10005
#define INF 2000000005
using namespace __gnu_pbds;
using namespace std;

struct edge{
    int fr,to,cap,flow,fe;
    edge(int fr,int to,int cap,int flow,int fe){
        this->fr=fr;
        this->to=to;
        this->cap=cap;
        this->flow=flow;
        this->fe=fe;
    }
    edge():fr(0),to(0),cap(0),flow(0),fe(0){}
};

vector<edge> e;
vector<int> geo[maxn];
int k,s,t,n,m,d[maxn],vis[maxn],p[maxn];

void Add_Edge(int fr,int to,int cap,int fe){
    e.push_back(edge(fr,to,cap,0,fe));
    geo[fr].push_back(k);
    e.push_back(edge(to,fr,0,0,-fe));
    geo[to].push_back(k^1);
    k+=2;
}

struct cmp{
    bool operator () (int x,int y){
        return d[x]>d[y];
    }
};

__gnu_pbds::priority_queue<int,cmp> spfa;

/*bool SPFA(){
    spfa.push(s);
    memset(vis,0,sizeof(vis));
    memset(p,-1,sizeof(p));
    for(int i=1;i<=n;i++){
        d[i]=INF;
    }
    d[s]=0;
    vis[s]=true;
    while(!spfa.empty()){
        int fr=spfa.top();
        spfa.pop();
        vis[fr]=false;
        for(int i=0;i<geo[fr].size();i++){
            edge op=e[geo[fr][i]];
            if(op.cap>op.flow&&d[op.to]>d[fr]+op.fe){
                p[op.to]=geo[fr][i];
                d[op.to]=d[fr]+op.fe;
                if(!vis[op.to]){
                    spfa.push(op.to);
                    vis[op.to]=true;
                }
            }
        }
    }
    if(d[t]>=INF)return false;
    return true;
}*/

bool SPFA(){
    spfa.push(t);
    memset(vis,0,sizeof(vis));
    memset(p,-1,sizeof(p));
    for(int i=1;i<=n;i++){
        d[i]=INF;
    }
    d[t]=0;
    vis[t]=true;
    while(!spfa.empty()){
        int fr=spfa.top();
        spfa.pop();
        vis[fr]=false;
        for(int i=0;i<geo[fr].size();i++){
            edge op=e[geo[fr][i]^1];
            if(op.cap>op.flow&&d[op.fr]>d[fr]+op.fe){
                p[op.fr]=geo[fr][i]^1;
                d[op.fr]=d[fr]+op.fe;
                if(!vis[op.fr]){
                    spfa.push(op.fr);
                    vis[op.fr]=true;
                }
            }
        }
    }
    if(d[s]>=INF)return false;
    return true;
}

/*void Augment(int& maxflow,int& mincost){
    int now=t,flow=INF;
    while(now!=s){
        edge op=e[p[now]];
        flow=min(flow,op.cap-op.flow);
        now=op.fr;
    }
    now=t;
    while(now!=s){
        int k=p[now];
        e[k].flow+=flow;
        e[k^1].flow-=flow;
        now=e[k].fr;
    }
    maxflow+=flow;
    mincost+=flow*d[t];
}*/

void Augment(int& maxflow,int& mincost){
    int now=s,flow=INF;
    while(now!=t){
        edge op=e[p[now]];
        flow=min(flow,op.cap-op.flow);
        now=op.to;
    }
    now=s;
    while(now!=t){
        int k=p[now];
        e[k].flow+=flow;
        e[k^1].flow-=flow;
        now=e[k].to;
    }
    maxflow+=flow;
    mincost+=flow*d[s];
}

void Mincost_Maxflow(int& maxflow,int& mincost){
    while(SPFA()){
        Augment(maxflow,mincost);
    }
}

void preprocess(){
    s=0,t=n+1;
    Add_Edge(0,1,2,0);
    Add_Edge(n,n+1,2,0);
    return;
}

int main(){
    /*freopen("input.txt","r",stdin);
    freopen("output.txt","w",stdout);*/
    scanf("%d%d%d%d",&n,&m,&s,&t);
    int x,y,z,k;
    //preprocess();
    for(int i=0;i<m;i++){
        scanf("%d%d%d%d",&x,&y,&z,&k);
        Add_Edge(x,y,z,k);
    }
    int maxflow=0,mincost=0;
    Mincost_Maxflow(maxflow,mincost);
    printf("%d %d",maxflow,mincost);
    return 0;
}

这里面注释掉的部分采用的是反向跑最短路以及增广,同时本代码采用的是Vector,如时间效率要求严格可改为前向星

算了还是贴一个用前向星的吧

#include<queue>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<ext/pb_ds/priority_queue.hpp>
#define maxn 10005
#define maxn2 200005
#define INF 2000000005
using namespace __gnu_pbds;
using namespace std;

struct edge{
    int to,cap,flow,fe,next;
    edge(int next,int to,int cap,int flow,int fe){
        this->next=next;
        this->to=to;
        this->cap=cap;
        this->flow=flow;
        this->fe=fe;
    }
    edge():next(0),to(0),cap(0),flow(0),fe(0){}
}e[maxn2];

int k=1,s,t,n,m,d[maxn],vis[maxn],p[maxn],h[maxn];

void Add_Edge(int fr,int to,int cap,int fe){
    e[++k]=edge(h[fr],to,cap,0,fe);
    h[fr]=k;
    e[++k]=edge(h[to],fr,0,0,-fe);
    h[to]=k;
}

struct cmp{
    bool operator () (int x,int y){
        return d[x]>d[y];
    }
};

__gnu_pbds::priority_queue<int,cmp> spfa;

bool SPFA(){
    spfa.push(s);
    memset(vis,0,sizeof(vis));
    memset(p,-1,sizeof(p));
    for(int i=1;i<=n;i++){
        d[i]=INF;
    }
    d[s]=0;
    vis[s]=true;
    while(!spfa.empty()){
        int fr=spfa.top();
        spfa.pop();
        vis[fr]=false;
        for(int i=h[fr];i!=0;i=e[i].next){
            edge op=e[i];
            if(op.cap>op.flow&&d[op.to]>d[fr]+op.fe){
                p[op.to]=i;
                d[op.to]=d[fr]+op.fe;
                if(!vis[op.to]){
                    spfa.push(op.to);
                    vis[op.to]=true;
                }
            }
        }
    }
    if(d[t]>=INF)return false;
    return true;
}

/*bool SPFA(){
    spfa.push(t);
    memset(vis,0,sizeof(vis));
    memset(p,-1,sizeof(p));
    for(int i=1;i<=n;i++){
        d[i]=INF;
    }
    d[t]=0;
    vis[t]=true;
    while(!spfa.empty()){
        int fr=spfa.top();
        spfa.pop();
        vis[fr]=false;
        for(int i=0;i<geo[fr].size();i++){
            edge op=e[geo[fr][i]^1];
            if(op.cap>op.flow&&d[op.fr]>d[fr]+op.fe){
                p[op.fr]=geo[fr][i]^1;
                d[op.fr]=d[fr]+op.fe;
                if(!vis[op.fr]){
                    spfa.push(op.fr);
                    vis[op.fr]=true;
                }
            }
        }
    }
    if(d[s]>=INF)return false;
    return true;
}*/

void Augment(int& maxflow,int& mincost){
    int now=t,flow=INF;
    while(now!=s){
        edge op=e[p[now]];
        flow=min(flow,op.cap-op.flow);
        now=e[p[now]^1].to;
    }
    now=t;
    while(now!=s){
        int k=p[now];
        e[k].flow+=flow;
        e[k^1].flow-=flow;
        now=e[k^1].to;
    }
    maxflow+=flow;
    mincost+=flow*d[t];
}

/*void Augment(int& maxflow,int& mincost){
    int now=s,flow=INF;
    while(now!=t){
        edge op=e[p[now]];
        flow=min(flow,op.cap-op.flow);
        now=op.to;
    }
    now=s;
    while(now!=t){
        int k=p[now];
        e[k].flow+=flow;
        e[k^1].flow-=flow;
        now=e[k].to;
    }
    maxflow+=flow;
    mincost+=flow*d[s];
}*/

void Mincost_Maxflow(int& maxflow,int& mincost){
    while(SPFA()){
        Augment(maxflow,mincost);
    }
}

void preprocess(){
    s=0,t=n+1;
    Add_Edge(0,1,2,0);
    Add_Edge(n,n+1,2,0);
    return;
}

int main(){
    /*freopen("input.txt","r",stdin);
    freopen("output.txt","w",stdout);*/
    scanf("%d%d%d%d",&n,&m,&s,&t);
    int x,y,z,k;
    //preprocess();
    for(int i=0;i<m;i++){
        scanf("%d%d%d%d",&x,&y,&z,&k);
        Add_Edge(x,y,z,k);
    }
    int maxflow=0,mincost=0;
    Mincost_Maxflow(maxflow,mincost);
    printf("%d %d",maxflow,mincost);
    return 0;
}

请无视上方的一堆卡常数优化

别的,大概还有一些,但是今天写的有点累了,就先搞到这里

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值