网络流算法总结

正在施工中。。。目测完不成了23333

前言

网络硫算法也是在省选等比赛中经常出现的一种算法,其模型。。。数量真的是太多了,数不过来了都。。。

而且网络流解决问题。。。都很巧妙。。。直接的模板题很少啦。。。主要是模型,构建网络流的模型是非常重要的。当然了,不会写也是不行的。。。

撒,让我们看一下网络流吧。。。

网络流算法

Dinic求最大流

简介

Dinic求法与朴素求法有一定的区别。

思想是:每次先使用BFS对整个网络进行分层,已经没有可行流的边将不会被访问到。如果在BFS过程中无法访问到终点,则视为算法结束。随后在DFS过程中,以刚才的分层为深度进行,一次DFS将会同时填充多条道路的流。然后重复这个过程,直到BFS无法访问到终点为止。

总结流程如下:

1.BFS对网络进行分层。如果无法访问到终点,则算法结束。
2.使用DFS进行可行流的填充。
3.重复步骤1。

另外,该算法也可以使用当前弧优化。

思想是:对于每个点,都连接了数条边,而有的边在某次可行流的填充中已经填满了,而这些边在本次DFS中,将不会再次发生改变,所以可以直接使用一个数组来代替邻接表中的head数组,略过已满流的边以达到节约时间的目的。

但是注意,每次重新分层后,都需要重置当前弧。

所以过程变成了这样:

1.初始化当前弧数组。
2.BFS对网络进行分层。如果无法访问到终点,则算法结束。
3.使用DFS进行可行流的填充。
4.重复步骤1。

没错就是这样啊哈哈哈哈。。。

编程实现

推荐去洛谷做一下P3376 【模板】网络最大流

标程就是标准的弧优化Dinic网络流。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;

class MF
{
    static const int MAXN=10005;
    static const int MAXE=1000005;
    static const int INF=2147480000;
    private:
        struct node{int u,v,w,next;};
        node edge[MAXE];
        int s,t,n,head[MAXN],cnt,f,level[MAXN],cur[MAXN];
        queue<int>q;
        bool inq[MAXN];
        bool bfs()
        {
            memset(level,0,sizeof(level));
            memset(inq,0,sizeof(inq));
            bool ok=false;
            inq[s]=true;
            q.push(s);
            while(!q.empty())
            {
                int u=q.front();q.pop();
                if(u==t) ok=true;
                for(int i=head[u];i>0;i=edge[i].next)
                {
                    int v=edge[i].v,w=edge[i].w;
                    if(w&&!inq[v])
                    {
                        level[v]=level[u]+1;
                        inq[v]=true;
                        q.push(v);
                    }
                }
            }
            return ok;
        }
        int dfs(int u,int flow)
        {
            if(u==t)
            {
                f+=flow;
                return flow;
            }
            int sum=0;
            for(int &i=cur[u];i>0&&flow;i=edge[i].next)
            {
                int v=edge[i].v,w=edge[i].w;
                if(level[v]!=level[u]+1||!w) continue;
                int a=dfs(v,min(flow-sum,w));
                edge[i].w-=a;
                edge[i^1].w+=a;
                sum+=a;
            }
            return sum;
        }
    public:
        void init(int _s,int _t,int _n)
        {
            n=_n;s=_s;t=_t;
            cnt=1;f=0;
            memset(head,0,sizeof(head));
        }
        void credge(int u,int v,int w)
        {
            edge[++cnt]=(node){u,v,w,head[u]};
            head[u]=cnt;
            edge[++cnt]=(node){v,u,0,head[v]};
            head[v]=cnt;
        }
        int solve()
        {
            while(bfs())
            {
                for(int i=1;i<=n;i++) cur[i]=head[i];
                dfs(s,INF);
            }
            return f;
        }
};
MF F;
int n,m,s,t;

int main()
{
    ios::sync_with_stdio(false);
    cin>>n>>m>>s>>t;
    F.init(s,t,n);
    for(int i=1;i<=m;i++)
    {
        int u,v,w;
        cin>>u>>v>>w;
        F.credge(u,v,w);
    }
    cout<<F.solve()<<endl;
    return 0;
}

SPFA求最大流最小费用

简介

使用SPFA来求最小费用最大流。。。

首先来讲,最大流最小费用是在保证最大流的情况下,使得花费的费用最小。

这里注意,每条边的费用是指单位流的费用。

具体思路是:每次使用SPFA来找出增广路径,然后增广。

关键在于使用SPFA,每次选择的路径是:一条从起点到终点的权值和最小的增广路径。

稍微多说一句,其他的最短路算法也是可行的,关键就在于最大流最小费用的特殊性质。因为增广过程中,每条边的流增加都是相同的,所以可以直接使用各边的权值作为增广的权值进行“预测”,然后找到权值和最小的路径增广。

总结一下:

1.使用最短路算法寻找增广路。
2.进行增广。
3.重复步骤1,直到无法到达终点。

编程实现

推荐去洛谷做一下P3381 【模板】最小费用最大流

但是请注意,由于洛谷近期加强了数据,所以使用SPFA的方法是无法拿到满分的。。。常数小一点的话,可以拿到80分,常数大点就只有70分了。。。

要想通过。。。应该需要将SPFA修改成堆优化的迪杰斯特拉吧。。。拒绝这样麻烦的修改。。。

以下程序的得分是80分。(明明原先都能过的啊啊啊)

得分情况:AAAAAAATTA

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;

class MFMC
{
    static const int MAXN=10005;
    static const int MAXE=1000005;
    static const int INF=2147480000;
    private:
        struct node{int u,v,w,c,next;};
        node edge[MAXE];
        int head[MAXN],cnt,s,t,n;
        queue<int>q;
        int spfa[MAXN];
        bool inq[MAXN];
        int path[MAXN];
        bool SPFA()
        {
            for(int i=1;i<=n;i++)
            {
                spfa[i]=INF;
                inq[i]=false;
            }
            inq[s]=true;
            spfa[s]=0;
            q.push(s);
            while(!q.empty())
            {
                int u=q.front();q.pop();
                for(int i=head[u];i>0;i=edge[i].next)
                {
                    int v=edge[i].v,c=edge[i].c,w=edge[i].w;
                    if(w&&spfa[v]>spfa[u]+c)
                    {
                        spfa[v]=spfa[u]+c;
                        path[v]=i;
                        if(!inq[v])
                        {
                            inq[v]=true;
                            q.push(v);
                        }
                    }
                }
                inq[u]=false;
            }
            if(spfa[t]==INF) return false;
            return true;
        }
        int dfs(int v,int nowf,int &flow,int &cost)
        {
            if(v==s)
            {
                flow+=nowf;
                return nowf;
            }
            int te=path[v];
            nowf=dfs(edge[te].u,min(nowf,edge[te].w),flow,cost);
            cost+=edge[te].c*nowf;
            edge[te].w-=nowf;
            edge[te^1].w+=nowf;
            return nowf;
        }
    public:
        void init(int _s,int _t,int _n)
        {
            n=_n;s=_s;t=_t;
            cnt=1;
            memset(head,0,sizeof(head));
        }
        void credge(int u,int v,int w,int c)
        {
            edge[++cnt]=(node){u,v,w,c,head[u]};
            head[u]=cnt;
            edge[++cnt]=(node){v,u,0,-c,head[v]};
            head[v]=cnt;
        }
        void solve(int &flow,int &cost)
        {
            flow=0;
            cost=0;
            while(SPFA())
            {
                int nzdl=INF;
                dfs(t,nzdl,flow,cost);
            }
        }
};
MFMC F;
int n,m,s,t;

int main()
{
    ios::sync_with_stdio(false);
    cin>>n>>m>>s>>t;
    F.init(s,t,n);
    for(int i=1;i<=m;i++)
    {
        int u,v,w,c;
        cin>>u>>v>>w>>c;
        F.credge(u,v,w,c);
    }
    int flow=0,cost=0;
    F.solve(flow,cost);
    cout<<flow<<" "<<cost;
    return 0;
}

常见网络流模型

对偶图

看这道题:

[BeiJing2006]狼抓兔子
现在小朋友们最喜欢的”喜羊羊与灰太狼”,话说灰太狼抓羊不到,但抓兔子还是比较在行的,而且现在的兔子还比较笨,它们只有两个窝,现在你做为狼王,面对下面这样一个网格的地形:
狼抓兔子
左上角点为(1,1),右下角点为(N,M)(上图中N=4,M=5).有以下三种类型的道路
1:(x,y)<==>(x+1,y)
2:(x,y)<==>(x,y+1)
3:(x,y)<==>(x+1,y+1)
道路上的权值表示这条路上最多能够通过的兔子数,道路是无向的. 左上角和右下角为兔子的两个窝,开始时所有的兔子都聚集在左上角(1,1)的窝里,现在它们要跑到右下解(N,M)的窝中去,狼王开始伏击这些兔子.当然为了保险起见,如果一条道路上最多通过的兔子数为K,狼王需要安排同样数量的K只狼,才能完全封锁这条道路,你需要帮助狼王安排一个伏击方案,使得在将兔子一网打尽的前提下,参与的狼的数量要最小。因为狼还要去找喜羊羊麻烦.

这个题使用对偶图优化。

什么?对偶图是什么?

对偶图,就是指一个图的每条边,必定会有两个面在其左右侧。

比如说:

对偶图

每个边都有两个面相邻(包含最外面的大的面)。

我们知道,网络流的最大流即是网络的最小割。

割:在图上画一条线阻断S→T的路径,被阻断的路径的权值和就是割。
最小割:一个图的割中最小的那个。

那么这个图由于具有对偶图的性质,那么我们可以这样转化成求最小割:

转化成最小割

如图所示,将最外侧的面分割成两个面。

然后,将每个面都作为一个点,然后将每条边的两侧的两个面用一条无向边连接起来(除了刚才分割开的最外侧的面),如图。

然后,跑一遍最短路算法即可,起点和终点就是刚才分割的两个面。

其实主要有难度的就是将对偶图转化为最小割的图,很容易混淆,涉及到一部分数学计算。

标准程序如下:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;

//#define DEBUG

const int MAXN=2000005;
const int MAXE=2000005<<2;
const int INF=2147480000;
class SPFA
{
    private:
        struct node{int u,v,w,next;};
        node edge[MAXE];
        int cnt,head[MAXN],spfa[MAXN];
        bool inq[MAXN];
        queue<int>q;

    public:
        void credge(int u,int v,int w)
        {
            #ifdef DEBUG
            cout<<"[EDGE] "<<u<<" <--"<<w<<"--> "<<v<<endl;
            #endif
            edge[++cnt]=(node){u,v,w,head[u]};
            head[u]=cnt;
            edge[++cnt]=(node){v,u,w,head[v]};
            head[v]=cnt;
        }
        int solve(int s,int t)
        {
            inq[s]=true;
            q.push(s);
            memset(spfa,0x3f,sizeof(spfa));
            spfa[s]=0;
            while(!q.empty())
            {
                int u=q.front();q.pop();
                inq[u]=false;
                for(int i=head[u];i>0;i=edge[i].next)
                {
                    int v=edge[i].v,w=edge[i].w;
                    if(spfa[v]>spfa[u]+w)
                    {
                        spfa[v]=spfa[u]+w;
                        if(!inq[v])
                        {
                            q.push(v);
                            inq[v]=true;
                        }
                    }
                }
            }
            return spfa[t];
        }
};
SPFA F;

int n,m,s,t,line;

bool SSSPJ1()//Special situation 1
{
    if(n==1||m==1)
    {
        int ans=INF,temp,ending=max(m,n)-1;
        while(--ending)
        {
            //readint(temp);
            cin>>temp;
            ans=min(ans,temp);
        }
        cout<<ans;
        return true;
    }
    return false;
}

void DRP_1()//Data reading precedure 1
{
    int temp;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=line;j++)
        {
            cin>>temp;
            if(i==1) F.credge(s,j,temp);
            else if(i==n) F.credge(2*(i-2)*line+line+j,t,temp);
            else F.credge(2*(i-2)*line+line+j,2*(i-2)*line+line+j+line,temp);
        }
    }
}

void DRP_2()//Data reading precedure 2
{
    int temp;
    for(int i=1;i<n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            cin>>temp;
            if(j==1) F.credge(line+(i-1)*line*2+j,t,temp);
            else if(j==m) F.credge((i-1)*2*line+j-1,s,temp);
            else F.credge((i-1)*2*line+j-1,(i-1)*2*line+j-1+m,temp);
        }
    }
}

void DRP_3()//Data reading precedure 3
{
    int temp;
    for(int i=1;i<n;i++)
    {
        for(int j=1;j<m;j++)
        {
            cin>>temp;
            F.credge((i-1)*line*2+j,(i-1)*line*2+j+line,temp);
        }
    }
}

int main()
{
    ios::sync_with_stdio(false);
    //readint(n);readint(m);
    cin>>n>>m;
    if(SSSPJ1()) return 0;
    line=m-1;
    s=1999999;t=s+1;
    DRP_1();
    DRP_2();
    DRP_3();
    cout<<F.solve(s,t);
    return 0;
}

标程建图方法:

建图方法

边的3种颜色代表了3个DRP的功能。这样分类相对来讲比较有条理(上下、左右、斜)

最大权闭合子图

最大权闭合子图致力于解决这样一个问题:

对于一个图,点有权值,有正权点,有负权点,问从起点走,走那些路能够使获得的点的权值和最大(没有固定终点,也可以不走)。

我们用网络流来解决这类问题,并统称他们为:最大权闭合子图问题。

解决方案如下:

1.将图读入,存储。
2.将图中从原点能够一次性到达的正权点向起点连接边,所有负权点向终点连接边。起点/终点向每一个点连接的边权值为点权的绝对值。
3.图中正常的边依旧按照原有关系进行连接,但是边权值全部为正无穷。
4.执行最大流算法。
5.将所有正权点的权值相加,减去最大流,即是需要的结果。

为什么呢?道洗呆?

======================施工(弃坑)现场======================

求最小点割集

二分图模型

【广告时间】我同学的博客也是棒棒哒,有兴趣来看看吧!

猜你喜欢

转载自blog.csdn.net/rentenglong2012/article/details/69486551