网络流学习笔记

随声附和 提交于 2020-02-02 00:37:36

最大流

​ 我再次博客上暂不叙述预留推进(SAP),事实是,我不会,暂且搁置,以后会补充

​ 最大流在实际问题中的应用是,你要从一个源点s送水至汇点t,这些点中有水管链接,水管的最大能通过的水有不同,让你求单位时间内你最多能送多少水。

​ 我们的流网络可以理解为一群有向边图

一些想法

​ 我们定义\(f(u,v)\)为u节点到v节点的流量。,\(c(u,v)\)为u节点到v节点的流量限制

​ 首先我们由于不能爆水管,有这个显而易见的式子
\[ 0 \le f(u,v) \le c(u,v) \]
​ 其次我们来,思考一下对于每个节点的的流出和流入,显然的我们可以把KCL(即Kirchhoff's Current Law),推广到oi界上,我们有
\[ \sum _{v\in V} f(v,u)=\sum_{v \in V} f(u,v) \]
想法结束进入正题

​ 经过度娘的帮助,我们知道一种叫做增广路的东西,在一个残余网络(就是说某些管子已经有水了),就是说从源节点到汇节点的一条能走水的路径

​ 这样我们知道,假如,我们一遍又一遍的操作之后,没有增广路可以找了,那么我们必定求出了最大流

​ 然而怎么寻找增广路,这是一个问题。

​ 经过思考之后我们可以用一个十分暴力的思想来解决,进行深搜,主要思想是

  1. 我们可以每一次从原点开始深搜
  2. 搜到汇节点,返回增广路上的最小权值、
  3. 重复第二步,知道找不到增广路

我们的具体实现,建反向边,把正的流顶回去的操作

具体实现

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
const int Maxn=201;

int n,m,min1,s,ans,t,u,v,w,Map[Maxn][Maxn],pre[Maxn];
bool flag[Maxn];
void dfs(int u){
    flag[u]=1;
    
    if(u==t) return ;
    for(int i=1;i<=n;i++){
        if(!flag[i]&&Map[u][i]>0){
            pre[i]=u;
            dfs(i);
            if(flag[t]){
                min1=min(min1,Map[u][i]);
                return ;
            }
        }
    }
    return ;
}
int main(){
//  freopen("flow.in","r",stdin);
    scanf("%d%d%d%d",&n,&m,&s,&t);
    
    for(int i=1;i<=m;i++){
        scanf("%d%d%d",&u,&v,&w);
        Map[u][v]+=w;
    }
    
    while(1){
//      printf("9999:\n");
        memset(pre,0,sizeof pre);
        memset(flag,0,sizeof flag);
        min1=0x3f3f3f3f;
        dfs(s);
        if(!flag[t]) break;
        int now=t;
        while(1){
            if(now==s) break;
            Map[pre[now]][now]-=min1;
            Map[now][pre[now]]+=min1;
            now=pre[now];
        }
    }
    printf("%d",ans);
    return 0;
}

​ 值得一提的有两点

  1. 如果你拿矩阵存,要对重边处理
  2. dfs 的类型。。。

​ 有兴趣去翻翻历史,EK和Dinic的渊源

​ 好了我们开始有一点DINIC的雏形了,dinic的优秀是在,他做了一个叫层次网络的东西,可以大大加快速度

​ 我们来说说Dinic算法的套路

  1. 对不饱和边进行广搜
  2. 搜到之后 按照上面的套路做深搜,要加一下是否满足层次网络
  3. 计算答案
  4. 返回1步,能搜到t则继续,否则退出循环

​ 而且有两个简单优化

  1. 当前弧
  2. 满流 ,好像是

我上代码了

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
const int Maxn=2*1e4+11,Maxm=2*1e5+11;
struct Edge{
    int fr,to,lac,wg;
}edge[Maxm];
int s,t,n,m,u,v,w,cnt,h[Maxn],dep[Maxn],ans,cur[Maxn];
bool flag[Maxn],vis[Maxn];
int read(){
    int x=0;
    char ch=getchar();
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9'){
        x=(x<<1)+(x<<3)+(ch-'0');
        ch=getchar();
    }
    return x;
}
void insert(int u,int v,int w){
    edge[cnt].to=v;
    edge[cnt].lac=h[u];
    edge[cnt].fr=u;
    edge[cnt].wg=w;
    h[u]=cnt++;
}
void bfs(int u){
    queue<int>q1;
    q1.push(u);
    dep[u]=0,flag[u]=1;
    while(!q1.empty()){
        int fr=q1.front();
        q1.pop();
        for(int i=h[fr];i!=-1;i=edge[i].lac){
            int to=edge[i].to;
            if((!edge[i].wg)||flag[to]) continue;
            q1.push(to);
            dep[to]=dep[fr]+1;
            flag[to]=1;
        }
    }
}
int dfs(int u,int min1){
    if(u==t) return min1;
    int sum=min1;
    flag[u]=1;
    for(int i=cur[u];i!=-1;i=edge[i].lac){
        int to=edge[i].to;
        if(edge[i].wg==0) continue;
        if(dep[to]!=dep[u]+1) continue;
        if(flag[to]) continue;
        cur[u]=i;//当前弧
        int ret1=dfs(to,min(sum,edge[i].wg));
        edge[i].wg-=ret1,edge[i^1].wg+=ret1;
        sum-=ret1;
        if(!sum) break;//满流
    }
    return min1-sum;
}
int main(){
//  freopen("Dinic.in","r",stdin);
    n=read(),m=read(),s=read(),t=read();
    memset(h,-1,sizeof h);
    for(int i=1;i<=m;i++){
        u=read(),v=read(),w=read();
        insert(u,v,w);
        insert(v,u,0);
    }
    while(1){
        memset(dep,-1,sizeof dep);
        memset(flag,0,sizeof flag);
        memcpy(cur,h,sizeof cur);
        bfs(s);
        if(dep[t]==-1) break;
        memset(flag,0,sizeof flag);
        int ret=dfs(s,0x3f3f3f3f);
        ans+=ret;
    }
    printf("%d",ans);
    return 0;
}

值得一提的是 用^,天下我有,

然后优化,一定要加,前人的经验呀,,,血与泪的教训

最小费用最大流

SFPA

把深搜和广搜,改成某个知名的的单源最短路求法就好了 ,慎用,f是经验

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
const int Maxm=150001,Maxn=5005;
struct Edge{
    int to,flo,co,lac;//co指cost ,flo 指 flow 
}edge[Maxm];
int n,m,h[Maxn],s,t,x,y,z,f,cnt,co[Maxn],flow[Maxn],pre[Maxn],maxflo,minco,last[Maxn];
bool vis[Maxn];
void insert(int x,int y,int z,int f){
    edge[cnt].to=y;
    edge[cnt].lac=h[x];
    edge[cnt].flo=z;
    edge[cnt].co=f;
    h[x]=cnt++;
}
bool SPFA(int s,int t){
    memset(flow,0x3f3f3f3f,sizeof flow);
    pre[t]=-1;
    memset(co,0x3f3f3f3f,sizeof co);
    memset(vis,0,sizeof vis);
    queue<int> q;
    q.push(s);vis[s]=1;co[s]=0,pre[s]=-1;
    while(!q.empty()){
        int fr=q.front();
        q.pop();
        vis[fr]=0;
        for(int i=h[fr];i!=-1;i=edge[i].lac){
            int to=edge[i].to;
            if(edge[i].flo>0&&co[to]>co[fr]+edge[i].co){
                co[to]=co[fr]+edge[i].co;
                pre[to]=fr;
                last[to]=i;
                flow[to]=min(flow[fr],edge[i].flo);
                if(!vis[to]){
                    q.push(to);
                    vis[to]=1;
                }
            } 
        }
    }
    return pre[t]!=-1;
}
void SFPA(){
    while(SPFA(s,t)){
        int now=t;
        maxflo+=flow[t];
        minco+=flow[t]*co[t];
        while (now!=s){
            edge[last[now]].flo-=flow[t];
            edge[last[now]^1].flo+=flow[t];
            now=pre[now];
        }
    }
}
int main() {
//  freopen("SFPA.in","r",stdin);
    memset(h,-1,sizeof h);
    scanf("%d%d%d%d",&n,&m,&s,&t);
    for(int i=1;i<=m;i++){
        scanf("%d%d%d%d",&x,&y,&z,&f);
        insert(x,y,z,f);
        insert(y,x,0,-f);
    }
    SFPA();
    printf("%d %d",maxflo,minco);
    return 0;
}

这个算法是用了广搜的思想,Dinic的骨架,其正确性来源于流量优先,而费用次之

注意记录前驱

咕,以后还会增加内容的,我累了。。

​ 记于2020年2月1日

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!