网络流

推荐一个比较好的博客:https://www.cnblogs.com/SYCstudio/p/7260613.html网络流,这类算法,第一步就是理解概念

1.从概念开始

——百度百科

当你看到这段定义时,会感觉很难理解,有好多术语。

但其中还是有几句有用的。

网络流,就是一个有向图,它的每条边被称为弧。

每条弧有一个容量限制,我们后面再提。

这个算法有什么用呢?

举个例子,我们现在有许多水从总站出发,要运到最后的终点,中间有许多的中转站。

我们可以把水分成许多份,走许多不同的线路到达终点。

但是水在流动时,容量不能超过每根水管的限度,现在问你最多可以运多少水一次性到达终点。

一次性的含义是:水是在同时流动的。

网络流的最大流算法,就是来解决这个问题的。

2.算法思想及实现

网络流中最朴素的算法就是EK算法了。

这里我们要给出许多定义。

1)链

链就相当于有向图中路径的概念,但他们有不同。

有向图中的路径必须是相邻两个节点有正方向的边相连,但链只是一个顶点序列。

2)前向弧和后向弧

沿着链,与链正方向一致的弧称为前向弧

与链正方向相反的弧称为后向弧

 记链为P,则包含前向弧的集合为P+,包含后向弧的集合为P-

3)增广路

P+中的每一条弧均为占满容量上限

P-中的每一条弧流量均不为0

这样的链P称为增广路。

4)残量和残量网络

残量理解为弧中剩余的容量,残量网络就是残量的网络集合。

从残量网络的定义可以看出,原容量网络中的每条弧在残量网络中都化为一条或两条弧。

显然,只要残量网络中存在增广路,流量就可以增大。

可以证明它的逆命题也成立:如果残量网络中不存在增广路,则当前流就是最大流。这就是著名的增广路定理

由增广路定理可以得出EK算法,它对整张图进行BFS,找到增广路后进行增广,复杂度为O(VE2)

由于EK算法太慢了,Dinic算法就诞生了。

Dinic算法引入了一个叫做分层图的概念。具体就是对于每一个点,我们根据从源点开始的bfs序列,为每一个点分配一个深度,然后我们进行若干遍dfs寻找增广路,每一次由u推出v必须保证v的深度必须是u的深度+1。

Dinic算法还有一个优化,这个优化被称为当前弧优化,即每一次dfs增广时不从第一条边开始,而是用一个数组cur记录点u之前循环到了哪一条边,以此来加速

代码:

// luogu-judger-enable-o2
#include<bits/stdc++.h>

#define rd(x) x=read()
#define N 100000
#define M 1000000
#define inf INT_MAX

using namespace std;

int s,t;
int cnt;
int head[N],next[M<<1],v[M<<1],w[M<<1],depth[N],cur[N];
int n,m;

inline int read()
{
    int f=1,x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
    return x*f;
}

void addEdge(int u1,int v1,int w1){cnt++,next[cnt]=head[u1],v[cnt]=v1,w[cnt]=w1,head[u1]=cnt;}

bool bfs()
{
    queue<int>q;
    while(!q.empty())q.pop();
    memset(depth,0,sizeof(depth));
    depth[s]=1;
    q.push(s);
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        for(int i=head[u];i!=-1;i=next[i])
            if(w[i]>0&&depth[v[i]]==0)
            {
                depth[v[i]]=depth[u]+1;
                q.push(v[i]);
            }
    }
    if(depth[t])return 1;
    return 0;
}//bfs找增广路

int dfs(int u,int dist)
{
    if(u==t)return dist;
    for(int & i=cur[u];i!=-1;i=next[i])
    //for(int i=head[u];i!=-1;i=next[i])
        if(depth[v[i]]==depth[u]+1&&w[i])
        {
            int dis=dfs(v[i],min(dist,w[i]));
            if(dis>0)
            {
                w[i]-=dis,w[i^1]+=dis;
                return dis;
            }
        }
    return 0;
}//dfs增广

int dinic()
{
    int ans=0;
    while(bfs())
    {
        for(int i=1;i<=n;i++)cur[i]=head[i];//当前弧优化记录cur
        while(int d=dfs(s,inf))ans+=d;
    }
    return ans;
}

int main()
{
    cnt=-1;
    memset(head,-1,sizeof(head));
    //memset(next,-1,sizeof(next));
    rd(n),rd(m),rd(s),rd(t);
    for(int i=1;i<=m;i++)
    {
        int u1,v1,w1;
        rd(u1),rd(v1),rd(w1);
        addEdge(u1,v1,w1);
        addEdge(v1,u1,0);
    }//加边
    
    int ans=dinic();
    printf("%d\n",ans);
    
    return 0;
}

 接下来,我们给每条边增加一个费用,问在最大流情况下的最小费用。

其实操作很简单,就是将Dinic中的BFS改成SPFA就行了。

代码:

 

// luogu-judger-enable-o2
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include<bits/stdc++.h>

#define N 100005
#define rd(x) x=read()

using namespace std;

int n,m;
int s,t;
int dis[N],pre[N],last[N],flow[N],vis[N];
int maxflow,mincost;
struct Edge{
    int to,next,flow,dis;
}e[N];
int head[N],cnt; 

inline int read()
{
    int f=1,x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
    return x*f;
}

void addEdge(int from,int to,int flow,int dis){e[++cnt].next=head[from],e[cnt].to=to,e[cnt].flow=flow,e[cnt].dis=dis,head[from]=cnt;}

bool spfa(int s,int t)
{
    queue<int>q; 
    memset(dis,0x7f,sizeof(dis));
    memset(flow,0x7f,sizeof(flow));
    memset(vis,0,sizeof(vis));
    q.push(s); 
    vis[s]=1,dis[s]=0,pre[t]=-1;
    while (!q.empty())
    {
        int u=q.front();
        q.pop();
        vis[u]=0;
        for(int i=head[u];i!=-1;i=e[i].next)
            if(e[i].flow>0&&dis[e[i].to]>dis[u]+e[i].dis)
            {
                dis[e[i].to]=dis[u]+e[i].dis,pre[e[i].to]=u,last[e[i].to]=i,flow[e[i].to]=min(flow[u],e[i].flow);
                if(!vis[e[i].to])
                {
                    vis[e[i].to]=1;
                    q.push(e[i].to);
                }
            }
    }
    return pre[t]!=-1;
}

void MCMF()
{
    while(spfa(s,t))
    {
        int now=t;
        maxflow+=flow[t];
        mincost+=flow[t]*dis[t];
        while(now!=s)
        { 
            e[last[now]].flow-=flow[t];
            e[last[now]^1].flow+=flow[t];
            now=pre[now];
        }
    }
}

int main()
{
    cnt=-1;
    memset(head,-1,sizeof(head)); 
    rd(n),rd(m),rd(s),rd(t);
    for(int i=1;i<=m;i++)
    {
        int u,v,w,f;
        rd(u),rd(v),rd(w),rd(f);
        addEdge(u,v,w,f),addEdge(v,u,0,-f);
    }
    MCMF();
    printf("%d %d\n",maxflow,mincost);
    
    return 0;
}

 

转载于:https://www.cnblogs.com/Robin20050901/p/10353948.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值