学习笔记:网络流

学习笔记 网络流

基本概念

一个网络是一张有向无环图,每条边有容量 \(w\),表示这条边最多能容纳多少流量,入度为0的点叫源点 \(s\),出度为0的点叫汇点 \(t\),源点有无限流量可以提供,最终要流向汇点,每个点流量守恒,即流入量等于流出量。

残量网络:找完增广路后并把边的容量相应更新后剩下的网络。

最大流

最大流问题是问从源点能流到汇点的最大流量是多少。

引入反向边:初始容量为0,当u给v流了w的流量,反向边容量增加w,当流量流经反向边时相当于在原边上退流,相当于提供了反悔机会。

增广路:从源点到汇点的一条流量可流过的路径。

最大流算法本质是找增广路。

EK算法

用队列增广,记录每个点的流向它的边last,增广到汇点后把边的容量更新,重复此过程直到找不到增广路。

复杂度\(O(nm^2)\)

点击查看代码
#include <bits/stdc++.h>
#define int long long
#define endl putchar('\n')
using namespace std;
const int M = 2e3+10;
const int inf = 2147483647;
typedef long long ll;
inline int read(){
    int x=0,f=0;char c=getchar();
    while(!isdigit(c)){
        if(c=='-') f=1;c=getchar();
    }
    do{
        x=(x<<1)+(x<<3)+(c^48);
    }while(isdigit(c=getchar()));
    return f?-x:x;
}
struct Edge{
    int t,w,next;
};
Edge e[M*10];int head[M],tot=1;
int n,m,s,t,flow[M],last[M];
int vis[M][M];
inline void add(int f,int t,int w){
    e[++tot].t=t;
    e[tot].w=w;
    e[tot].next=head[f];
    head[f]=tot;
}
bool Find(){
    memset(last,-1,sizeof(last));
    flow[s]=inf;
    queue<int> q;
    q.push(s);
    while(!q.empty()){
        int x=q.front();
        q.pop();
        if(x==t) break;
        for(int i=head[x];i;i=e[i].next){
            int v=e[i].t,w=e[i].w;
            if(w>0&&last[v]==-1){
                last[v]=i;
                flow[v]=min(flow[x],w);
                q.push(v);
            }
        }
    }
    return last[t]!=-1;
}
int EK(){
    int maxflow=0;
    while(Find()){
        maxflow+=flow[t];
        for(int i=t;i!=s;i=e[last[i]^1].t){
            e[last[i]].w-=flow[t];
            e[last[i]^1].w+=flow[t];
        }
    }
    return maxflow;
}
signed main(){
   
    n=read();m=read();s=read();t=read();
    for(int i=1;i<=m;i++){
        int u=read(),v=read(),w=read();
        if(!vis[u][v]){
            add(u,v,w);
            vis[u][v]=tot;
            add(v,u,0);
        }
        else{
            e[vis[u][v]].w+=w;
        }
    }
    printf("%lld\n",EK());
    return 0;
}

Dinic算法

先BFS划出分层图,每次DFS找增广路时都往下一层增广
当前弧优化:记录每个点起始遍历的边,保证每个点起始的每条边只被遍历一次

复杂度\(O(n^2m)\)

点击查看代码
#include <bits/stdc++.h>
#define int long long
#define endl putchar('\n')
using namespace std;
const int M = 1e4+10;
const int inf = 2147483647;
typedef long long ll;
inline int read(){
    int x=0,f=0;char c=getchar();
    while(!isdigit(c)){
        if(c=='-') f=1;c=getchar();
    }
    do{
        x=(x<<1)+(x<<3)+(c^48);
    }while(isdigit(c=getchar()));
    return f?-x:x;
}
struct Edge{
    int t,w,next;
};
Edge e[M<<1];int head[M],tot=1;
int n,m,s,t,lev[M],cur[M];
inline void add(int f,int t,int w){
    e[++tot]=(Edge){t,w,head[f]};head[f]=tot;
    e[++tot]=(Edge){f,0,head[t]};head[t]=tot;
}
bool bfs(){
    memset(lev,-1,sizeof(lev));
    memcpy(cur,head,sizeof(head));
    lev[s]=0;
    queue<int> q;
    q.push(s);
    while(!q.empty()){
        int x=q.front();q.pop();
        for(int i=head[x];i;i=e[i].next){
            int v=e[i].t,w=e[i].w;
            if(w>0&&lev[v]==-1){
                lev[v]=lev[x]+1;
                q.push(v);
            }
        }
    }
    return lev[t]!=-1;
}
int dfs(int u,int flow){
    if(flow<=0||u==t) return flow;
    int rest=flow;
    for(int i=cur[u];i&&rest;i=e[i].next){
        cur[u]=i;
        int v=e[i].t,w=e[i].w;
        if(w>0&&lev[v]==lev[u]+1){
            int res=dfs(v,min(rest,w));
            rest-=res;
            e[i].w-=res;
            e[i^1].w+=res;
        }
    }
    return flow-rest;
}
int dinic(){
    int ans=0;
    while(bfs()) ans+=dfs(s,inf);
    return ans;
}
signed main(){
    n=read();m=read();s=read();t=read();
    for(int i=1;i<=m;i++){
        int u=read(),v=read(),w=read();
        add(u,v,w);
    }
    printf("%lld\n",dinic());
    return 0;
}

费用流

每条边多了一个费用\(c\),每流过1单位流量就花费\(c\)

最小费用最大流:在保证是最大流的前提下使费用最小。

spfa费用流

类似EK算法,但是把增广部分改成了spfa,每次增广更新最短路,每个流量为flow的增广路会新产生 \(flow*dis[t]\) 的费用

点击查看代码
#include <bits/stdc++.h>
#define endl putchar('\n')
using namespace std;
const int M = 5e3+10;
const int inf = 2147483647;
inline int read(){
    int x=0,f=0;char c=getchar();
    while(!isdigit(c)){
        if(c=='-') f=1;c=getchar();
    }
    do{
        x=(x<<1)+(x<<3)+(c^48);
    }while(isdigit(c=getchar()));
    return f?-x:x;
}
struct Edge{
    int t,w,c,next;
};
Edge e[M*40];int head[M],tot=1;
int n,m,s,t,ans1,ans2,dis[M],flow[M],last[M],inq[M];
queue<int> q;
inline void add(int f,int t,int w,int c){
    e[++tot]=(Edge){t,w,c,head[f]};head[f]=tot;
    e[++tot]=(Edge){f,0,-c,head[t]};head[t]=tot;
}
bool spfa(){
    memset(dis,0x3f,sizeof(dis));
    memset(inq,0,sizeof(inq));
    memset(last,-1,sizeof(last));
    dis[s]=0;
    flow[s]=inf;
    q.push(s);
    while(!q.empty()){
        int x=q.front();q.pop();
        inq[x]=0;
        for(int i=head[x];i;i=e[i].next){
            int v=e[i].t,w=e[i].w,c=e[i].c;
            if(w>0&&dis[v]>dis[x]+c){
                dis[v]=dis[x]+c;
                last[v]=i;
                flow[v]=min(flow[x],w);
                if(!inq[v]){
                    inq[v]=1;
                    q.push(v);
                }
            }
        }
    }
    return last[t]!=-1;
}
void EK(){
    while(spfa()){
        ans1+=flow[t];
        ans2+=flow[t]*dis[t];
        for(int i=t;i!=s;i=e[last[i]^1].t){
            e[last[i]].w-=flow[t];
            e[last[i]^1].w+=flow[t];
        }
    }
}
int main(){
    n=read();m=read();s=read();t=read();
    for(int i=1;i<=m;i++){
        int u=read(),v=read(),w=read(),c=read();
        add(u,v,w,c);
    }
    EK();
    printf("%d %d\n",ans1,ans2);
    return 0;
}

上下界网络流

每条边有了最低流量限制 \(L\) 和最高流量限制 \(R\)

无源汇上下界可行流

先假设所有边的流量都是他们的流量下界 \(L\),上界相应变成 \(R-L\),此时不一定满足流量守恒,对于一个点 \(i\),设它的流入量为 \(x\),流出量为 \(y\),建立虚拟源点 \(ss\) 和虚拟汇点 \(tt\)。若 \(y>x\),也就是要附加流要流进来更多,则要给多的流量找去路,连一条从 \(i\)\(tt\) 容量为 \(x-y\) 的边,同理若 \(x>y\),则连一条 \(ss\)\(i\) 的容量为 \(y-x\) 的边

\(s\) 连出去的边可以满流则说明存在可行流

此时每条边流量为每条边的反向边容量加上它的下界

有源汇上下界可行流

连一条从 \(t\)\(s\) 下界为0上界为 \(inf\) 的边即可

有源汇上下界最大流

先求一遍可行流 \(flow1\),删去 \(t\)\(s\) 的边后在残量网络上跑从\(s\)\(t\) 的最大流 \(flow2\),答案即为 \(flow1+flow2\)

有源汇上下界最小流

同上,但最后改成跑从 \(t\)\(s\) 的最大流,答案为 \(flow1-flow2\)

posted @ 2023-01-02 17:09  blue_tsg  阅读(60)  评论(0编辑  收藏  举报