网络流入门——算法模板,习题和解析

最近一段时间再搞网络流,今天终于搞完了!!!!QAQ,好累呀。

首先是关于网络流的基础知识,这部分东西有点多,就不在这里说了,给几个有用的资源。

先推荐一下建图的博客:链式向前星,静态链表和邻接矩阵建图

之后就是网络流入门的知识,可以看刘汝佳的紫书里面的知识和这几个博客

网络流--最大流          数据结构与算法分析 - 网络流入门(Network Flow)

知道一些基础知识之后,就可以去用比较简单的增广路算法去做几道水题。

hdu1532最大流模板题   hdu3549——最大流模板题,这两个题不给代码了,都很简单。

我们现在估计应该都知道最大流问题了,我在这里给出基于EK算法实现的最大流算法,其实就是BFS。

抛代码模板

先给出基于邻接表的实现(来源刘汝佳紫书

struct Edge{
    int from,to,cap,flow;
    Edge(int u,int v,int c,int f):from(u),to(v),cap(c),flow(f){}
};

struct EdmondsKarp{
    int n,m;
    vector<Edge>edges;//边数的两倍
    vector<int>G[maxn];//邻接表,G[i][j]表示结点i的第j条边在e数组中的序号
    int a[maxn];//当起点到i的可改进量
    int p[maxn];//最短路树上p的入弧编号

    void init(int n){
        for(int i=0;i<n;i++)G[i].clear();
        edges.clear();
    }

    void AddEdge(int from,int to,int cap){
        edges.push_back(Edge(from,to,cap,0));
        edges.push_back(Edge(to,from,0,0));//反向弧
        m=edges.size();
        G[from].push_back(m-2);
        G[to].push_back(m-1);
    }

    int Maxflow(int s,int t){
        int flow=0;
        for(;;){
            memset(a,0,sizeof(a));
            queue<int>Q;
            Q.push(s);
            a[s]=INF;
            while(!Q.empty()){
                int x=Q.front();Q.pop();
                for(int i=0;i<G[x].size();i++){
                    Edge &e=edges[G[x][i]];
                    if(!a[e.to]&&e.cap>e.flow){
                        p[e.to]=G[x][i];
                        a[e.to]=min(a[x],e.cap-e.flow);
                        Q.push(e.to);
                    }
                }
                if(a[t])break;
            }
            if(!a[t])break;
            for(int u=t;u!=s;u=edges[p[u]].from){
                edges[p[u]].flow+=a[t];
                edges[p[u]^1].flow-=a[t];
            }
            flow+=a[t];
        }
        return flow;
    }
}EK;

然后是基于邻接矩阵的实现,比较简单

#include <iostream>
#include <queue>
#include<string.h>
using namespace std;
#define arraysize 201
int maxData = 0x7fffffff;
int capacity[arraysize][arraysize]; //记录残留网络的容量
int flow[arraysize];                //标记从源点到当前节点实际还剩多少流量可用
int pre[arraysize];                 //标记在这条路径上当前节点的前驱,同时标记该节点是否在队列中
int n,m;
queue<int> myqueue;
int BFS(int src,int des)
{
    int i,j;
    while(!myqueue.empty())       //队列清空
        myqueue.pop();
    for(i=1;i<m+1;++i)
    {
        pre[i]=-1;
    }
    pre[src]=0;
    flow[src]= maxData;
    myqueue.push(src);
    while(!myqueue.empty())
    {
        int index = myqueue.front();
        myqueue.pop();
        if(index == des)            //找到了增广路径
            break;
        for(i=1;i<m+1;++i)
        {
            if(i!=src && capacity[index][i]>0 && pre[i]==-1)
            {
                 pre[i] = index; //记录前驱
                 flow[i] = min(capacity[index][i],flow[index]);   //关键:迭代的找到增量
                 myqueue.push(i);
            }
        }
    }
    if(pre[des]==-1)      //残留图中不再存在增广路径
        return -1;
    else
        return flow[des];
}
int maxFlow(int src,int des)
{
    int increasement= 0;
    int sumflow = 0;
    while((increasement=BFS(src,des))!=-1)
    {
         int k = des;          //利用前驱寻找路径
         while(k!=src)
         {
              int last = pre[k];
              capacity[last][k] -= increasement; //改变正向边的容量
              capacity[k][last] += increasement; //改变反向边的容量
              k = last;
         }
         sumflow += increasement;
    }
    return sumflow;
}
int main()
{
    int i,j;
    int start,end,ci;
    while(cin>>n>>m)
    {
        memset(capacity,0,sizeof(capacity));
        memset(flow,0,sizeof(flow));
        for(i=0;i<n;++i)
        {
            cin>>start>>end>>ci;
            if(start == end)               //考虑起点终点相同的情况
               continue;
            capacity[start][end] +=ci;     //此处注意可能出现多条同一起点终点的情况
        }
        cout<<maxFlow(1,m)<<endl;
    }
    return 0;
}

知道最大流之后,就可以学一下最小割最大流定理,其实就是最大流的另一种表现形式,换了一种问的方式,结果一样。

最小割是指把两个点割开所花费的最低成本,定理就是最小割等于最大流。

至于证明可以去网上搜一下。

然后讲一下最大流进阶的算法(Dinic算法和SAP算法以及优化的ISAP算法)

Dinic算法的讲解可以看这篇文章:Dinic算法(研究总结,网络流)

SAP算法和ISAP算法的讲解可以看这个:网络流-最大流问题 ISAP 算法解释

总的最大流常用算法的时间复杂度为:ISAP>SAP>Dinic>EK≈FF

下面抛代码

基于邻接表的Dinic

#include<stdio.h>
#include<string.h>
#include<queue>
#include<vector>
#include<algorithm>
using namespace std;
const int maxn = 1000 + 10;
const int INF = 0x3f3f3f3f;
struct Edge
{
    int from, to, cap, flow;
    Edge(int u, int v, int c, int f) : from(u), to(v), cap(c), flow(f) {}
};
struct Dinic
{
    int n, m, s, t;//结点数,边数,(包括反向弧),源点编号和汇点编号 
    vector<int> G[maxn];//边表,edges[e]与edges[e^1]互为反向弧 
    vector<Edge> edges;//邻接表,G[i][j]表示结点 i 的第 j 条边在 e 数组的序号 
    bool vis[maxn];//BFS使用 
    int d[maxn]; //从起点到 i 的距离 
	int cur[maxn];//当前弧下标 
    void init(int n)
    {
        this->n = n;
        for(int i = 0; i <= n; i++)
            G[i].clear();
        edges.clear();
    }
    void AddEdge(int from, int to, int cap)
    {
        edges.push_back(Edge(from, to, cap, 0));
        edges.push_back(Edge(to, from, 0, 0));
        m = edges.size();
        G[from].push_back(m-2);
        G[to].push_back(m-1);
    }
    bool bfs()
    {
        memset(vis, false, sizeof(vis));
        queue<int> Q;
        d[s] = 0;
        Q.push(s);
        vis[s] = true;
        while(!Q.empty())
        {
            int x = Q.front();
          	Q.pop();
            for(int i = 0; i < G[x].size(); i++)
            {
                Edge& e = edges[G[x][i]];
                if(!vis[e.to] && e.cap>e.flow)//只考虑残量网络中的弧 
                {
                    d[e.to] = d[x] + 1;
                    Q.push(e.to);
                    vis[e.to] = true;
                }
            }
        }
        return vis[t];
    }
    int dfs(int x, int a)
    {
        if(x==t || a==0)
            return a;
        int f, flow = 0;
        for(int& i = cur[x]; i < G[x].size(); i++)//当前弧优化 
        {
            Edge& e = edges[G[x][i]];
            if(d[e.to]==d[x]+1 && (f=dfs(e.to, min(a, e.cap-e.flow)))>0)
            {
                e.flow += f;
                edges[G[x][i]^1].flow -= f;
                flow += f;
            	a -= f;
                if(a == 0)
                    break;
            }
        }
        return flow;
    }
    int max_flow(int s, int t)
    {
        this->s = s;
        this->t = t;
        int flow = 0;
        while(bfs())
        {
            memset(cur, 0, sizeof(cur));
            flow += dfs(s, INF);
        }
        return flow;
    }
}D;
int main(){
	int n,m;
    while(scanf("%d%d", &n, &m) != EOF){
        D.init(n+1);
        int u, v, w;
        for(int i = 0; i < m; i++){
            scanf("%d%d%d", &u, &v, &w);
            D.AddEdge(u, v, w);
            D.AddEdge(v, u, 0);
        }
        printf("%d\n", D.max_flow(1,n));
    }
    return 0;
}

基于邻接矩阵的Dinic算法

#include <bits/stdc++.h>
using namespace std;
const int maxn=450;
int n,m,dis[maxn],E[maxn][maxn];
bool bfs(int s)
{
	memset(dis,-1,sizeof(dis));
	queue<int> q;
	dis[s]=0;
	while(!q.empty()){
		int now=q.front();
		q.pop();
		for(int i = 1;i<=n;i++){
			if(dis[i]<0&&E[now][i]>0){
				dis[i]=dis[now]+1;
				q.push(i); 
			}
		} 
	}
	if(dis[n]>0) return true;
	else return false;
}
int Find(int x,int low)
{
	int a=0;
 	if(x==n) return low;
 	for(int i = 1;i<=n;i++){
 		if(E[x][i]>0&&dis[i]==dis[x]+1&&(a=Find(1,min(low,E[x][i])))){
 			E[x][i]-=a;
 			E[i][x]+=a;
 			return a;
		}
	}
    return 0;
}
int main(int argc, char** argv) {
	while(cin>>n>>m){
		memset(E,0,sizeof(E));
		for(int i = 1;i<=m;i++){
			int u,v,w;
			cin>>u>>v>>w;
			E[u][v]+=w;
		}
		int ans=0;
		while(bfs(1)){
			int tmp;
			while(tmp=Find(1,INT_MAX)){
				ans+=tmp;
			}
		}
		cout<<ans<<endl;
	}
	return 0;
}

基于邻接表的ISAP算法


#include <queue>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int MAXN = 5010;
const int MAXN_INT = (1 << 29);
struct Edge{
    int v, w, nxt;
};
bool isFind;
int head[MAXN];
Edge edge[MAXN];
int dis[MAXN], gap[MAXN];
int n, m, ecnt, aug, maxFlow;
void init(){
    ecnt = maxFlow = 0;
    memset(gap, 0, sizeof(gap));
    memset(dis, 0, sizeof(dis));
    memset(edge, 0, sizeof(edge));
    memset(head, -1, sizeof(head));
    gap[0] = n;
}
void addEdge(int u, int v, int w){
    edge[ecnt].v = v;
    edge[ecnt].w = w;
    edge[ecnt].nxt = head[u];
    head[u] = ecnt++;
}
void Find(int s){
    int dx, augc, minDis;
    if(s == n){
        isFind = true;
        maxFlow += aug;
        return;
    }
    augc = aug;
    minDis = n - 1;
    for(int i = head[i]; i + 1; i = edge[i].nxt){
        if(edge[i].w > 0){
            if(dis[s] == dis[edge[i].v] + 1){
                aug = min(aug, edge[i].w);
                Find(edge[i].v);
                if(dis[1] >= n) return;
                if(isFind){
                    dx = i;
                    break;
                }
                aug = augc;
            }
            minDis = min(minDis, dis[edge[i].v]);
        }
    }
    if(!isFind){
        gap[dis[s]]--;
        if(gap[dis[s]] == 0) dis[1] = n;
        dis[s] = minDis + 1;
        gap[dis[s]]++;
    }else{
        edge[dx].w -= aug;
        edge[dx ^ 1].w += aug;
    }
}
int main(){
    while(scanf("%d%d", &n, &m) != EOF){
        init();
        int u, v, w;
        for(int i = 0; i < m; i++){
            scanf("%d%d%d", &u, &v, &w);
            addEdge(u, v, w);
            addEdge(v, u, 0);
        }
        while(dis[1] < n){
            isFind = 0;
            aug = MAXN_INT;
            Find(1);
        }
        cout << maxFlow << endl;
    }
    return 0;
}

基于邻接矩阵的ISAP算法

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define MAXN 222
#define inf 100000000+1000
int map[MAXN][MAXN];//存图
int pre[MAXN];//记录当前点的前驱
int level[MAXN];//记录距离标号
int gap[MAXN];//gap常数优化
int NV,NE;

//入口参数vs源点,vt汇点
int SAP(int vs,int vt)
{
	memset(pre,-1,sizeof(pre));
	memset(level,0,sizeof(level));
	memset(gap,0,sizeof(gap));
	gap[0]=vt;
	int v,u=pre[vs]=vs,maxflow=0,aug=inf;
	
	while(level[vs]<vt)
	{
		//寻找可行弧
		for(v=1;v<=vt;v++)
		{
		    if(map[u][v]>0&&level[u]==level[v]+1){
		         break;
		    }
		}
		if(v<=vt)
		{
		   pre[v]=u;
	       u=v;
		   if(v==vt)
		   {
		   	     int neck=0;
		         aug=inf;
		         //寻找当前找到的一条路径上的最大流 , (瓶颈边)
		         for(int i=v;i!=vs;i=pre[i])
				 {
		             if(aug>map[pre[i]][i])
					 {
					 	aug=map[pre[i]][i];
						neck=i;
					 }
		         }
		         maxflow+=aug;
		         //更新残留网络
		         for(int i=v;i!=vs;i=pre[i]){
		             map[pre[i]][i]-=aug;
		             map[i][pre[i]]+=aug;
		        }
			    u=vs;		  //从源点开始继续搜
		//		u=neck; 	  // Dnic 多路增广优化,下次增广时,从瓶颈边(后面)开始
		    }
		}
		else
		{
		    //找不到可行弧
		    int minlevel=vt;
		    //寻找与当前点相连接的点中最小的距离标号
		    for(v=1;v<=vt;v++){
		         if(map[u][v]>0&&minlevel>level[v]){
		             minlevel=level[v];
		         }
		    }
		    gap[level[u]]--;//(更新gap数组)当前标号的数目减1;
		    if(gap[level[u]]==0)break;//出现断层
		    level[u]=minlevel+1;
		    gap[level[u]]++;
		    u=pre[u];
		}
	}
	return maxflow;
}

int main()
{
	int n,m,u,v,cap;
	while(~scanf("%d%d",&m,&n))
	{
		memset(map,0,sizeof(map));
		for(int i=1;i<=m;i++)
		{
		    scanf("%d%d%d",&u,&v,&cap);
		    map[u][v]+=cap;
		}
		printf("%d\n",SAP(1,n));
	}
	return 0;
}

下面进入另一个经典的网络流问题,最小费用最大流问题。

其实就是边多了一个权值,费用,然后在求所有的最大流里面最小的费用的最大流。

关于这个算法的知识可以参考这篇文章:网络流(六)最小费用最大流问题

也可以看紫书里面的内容,废话不多说,上大白书里面的模板

struct Edge{
    int from,to,cap,flow,cost;
    Edge(int u,int v,int c,int f,int w):from(u),to(v),cap(c),flow(f),cost(w){}
};

struct MCMF{
    int n,m;
    vector<Edge>edges;
    vector<int>G[maxn];
    int inq[maxn];//是否在队列中
    int d[maxn];//Bellman-Ford
    int p[maxn];//上一条弧
    int a[maxn];//可改进量

    void init(int n){
        this->n=n;
        for(int i=0;i<n;i++)G[i].clear();
        edges.clear();
    }

    void AddEdge(int from,int to,int cap,int cost){
        edges.push_back(Edge(from,to,cap,0,cost));
        edges.push_back(Edge(to,from,0,0,-cost));
        m=edges.size();
        G[from].push_back(m-2);
        G[to].push_back(m-1);
    }

    bool BellmanFord(int s,int t,int &flow,long long &cost){
        for(int i=0;i<n;i++)d[i]=INF;
        memset(inq,0,sizeof(inq));
        d[s]=0;inq[s]=1;p[s]=0;a[s]=INF;

        queue<int>Q;
        Q.push(s);
        while(!Q.empty()){
            int u=Q.front();Q.pop();
            inq[u]=0;
            for(int i=0;i<(int)G[u].size();i++){
                Edge &e=edges[G[u][i]];
                if(e.cap>e.flow&&d[e.to]>d[u]+e.cost){
                    d[e.to]=d[u]+e.cost;
                    p[e.to]=G[u][i];
                    a[e.to]=min(a[u],e.cap-e.flow);
                    if(!inq[e.to]){Q.push(e.to);inq[e.to]=1;}
                }
            }
        }
        if(d[t]==INF)return false;
        flow+=a[t];
        cost+=(long long)d[t]*(long long)a[t];
        for(int u=t;u!=s;u=edges[p[u]].from){
            edges[p[u]].flow+=a[t];
            edges[p[u]^1].flow-=a[t];
        }
        return true;
    }

    //需要保证初始网络中没有负权圈
    int MincostMaxflow(int s,int t,long long &cost){
        int flow=0;cost=0;
        while(BellmanFord(s,t,flow,cost));
        return flow;
    }
}MM;

其实关于最小费用最大流的求法还有两种,SPFA以及zkw最小费用流(适用于二分图)

基于SPFA的实现

struct Edge{
    int to,next,cap,flow,cost;
}edge[MAXM];
int head[MAXN],tol;
int pre[MAXN],dis[MAXN];
bool vis[MAXN];
int N;//节点总个数,节点编号从0~N-1
void init(int n){
    N=n;
    tol=0;
    memset(head,-1,sizeof(head));
}
void addedge(int u,int v,int cap,int cost){
    edge[tol].to=v;
    edge[tol].cap=cap;
    edge[tol].cost=cost;
    edge[tol].flow=0;
    edge[tol].next=head[u];
    head[u]=tol++;
    edge[tol].to=u;
    edge[tol].cap=0;
    edge[tol].cost=-cost;
    edge[tol].flow=0;
    edge[tol].next=head[v];
    head[v]=tol++;
}
bool spfa(int s,int t){
    queue<int>q;
    for(int i=0;i<N;i++){
        dis[i]=INF;
        vis[i]=false;
        pre[i]=-1;
    }
    dis[s]=0;
    vis[s]=true;
    q.push(s);
    while(!q.empty()){
        int u=q.front();
        q.pop();
        vis[u]=false;
        for(int i=head[u];i!=-1;i=edge[i].next){
            int v=edge[i].to;
            if(edge[i].cap>edge[i].flow&&dis[v]>dis[u]+edge[i].cost){
                dis[v]=dis[u]+edge[i].cost;
                pre[v]=i;
                if(!vis[v]){
                    vis[v]=true;
                    q.push(v);
                }
            }
        }
    }
    if(pre[t]==-1)return false;
    else return true;
}
//返回的是最大流,cost存的是最小费用
int minCostMaxflow(int s,int t,int &cost){
    int flow=0;
    cost=0;
    while(spfa(s,t)){
        int Min=INF;
        for(int i=pre[t];i!=-1;i=pre[edge[i^1].to]){
            if(Min>edge[i].cap-edge[i].flow)
                Min=edge[i].cap-edge[i].flow;
        }
        for(int i=pre[t];i!=-1;i=pre[edge[i^1].to]){
            edge[i].flow+=Min;
            edge[i^1].flow-=Min;
            cost+=edge[i].cost*Min;
        }
        flow+=Min;
    }
    return flow;
}

基于zkw费用流的实现

struct Edge{
    int to,next,cap,flow,cost;
    Edge(int _to=0,int _next=0,int _cap=0,int _flow=0,int _cost=0):
        to(_to),next(_next),cap(_cap),flow(_flow),cost(_cost){}
}edge[MAXM];
struct ZKW_MinCostMaxFlow{
    int head[MAXN],tot;
    int cur[MAXN];
    int dis[MAXN];
    bool vis[MAXN];
    int ss,tt,N;//源点、汇点和点的总个数(编号是0~N-1),不需要额外赋值,调用会直接赋值
    int min_cost,max_flow;
    void init(){
        tot=0;
        memset(head,-1,sizeof(head));
    }
    void addedge(int u,int v,int cap,int cost){
        edge[tot]=Edge(v,head[u],cap,0,cost);
        head[u]=tot++;
        edge[tot]=Edge(u,head[v],0,0,-cost);
        head[v]=tot++;
    }
    int aug(int u,int flow){
        if(u==tt)return flow;
        vis[u]=true;
        for(int i=cur[u];i!=-1;i=edge[i].next){
            int v=edge[i].to;
            if(edge[i].cap>edge[i].flow&&!vis[v]&&dis[u]==dis[v]+edge[i].cost){
                int tmp=aug(v,min(flow,edge[i].cap-edge[i].flow));
                edge[i].flow+=tmp;
                edge[i^1].flow-=tmp;
                cur[u]=i;
                if(tmp)return tmp;
            }
        }
        return 0;
    }
    bool modify_label(){
        int d=INF;
        for(int u=0;u<N;u++)
            if(vis[u])
                for(int i=head[u];i!=-1;i=edge[i].next){
                    int v=edge[i].to;
                    if(edge[i].cap>edge[i].flow&&!vis[v])
                        d=min(d,dis[v]+edge[i].cost-dis[u]);
                }
        if(d==INF)return false;
        for(int i=0;i<N;i++)
            if(vis[i]){
                vis[i]=false;
                dis[i]+=d;
            }
        return true;
    }
    /*
    直接调用获取最小费用和最大流
    输入:start-源点,end-汇点,n-点的总个数(编号从0开始)
    返回值:pair<int,int>第一个是最小费用,第二个是最大流
    */
    pair<int,int> mincostmaxflow(int start,int end,int n){
        ss=start,tt=end,N=n;
        min_cost=max_flow=0;
        for(int i=0;i<n;i++)dis[i]=0;
        while(1){
            for(int i=0;i<n;i++)cur[i]=head[i];
            while(1){
                for(int i=0;i<n;i++)vis[i]=false;
                int tmp=aug(ss,INF);
                if(tmp==0)break;
                max_flow+=tmp;
                min_cost+=tmp*dis[ss];
            }
            if(!modify_label())break;
        }
        return make_pair(min_cost,max_flow);
    }
};

下面的才是真正的重点——建模和模型的变换

资料:

最详细(也可能现在不是了)网络流建模基础

算法竞赛入门经典——训练指南(简称大白书,真的是经典,有的你第一次看看不懂,等真正做题时才能意识到)

主要内容:

建模的方法和技巧。以下来自白书

方法:

1.多源多汇问题:有多个源点多个汇点,构造一个超级汇点和超级源点,超级汇点和汇点相连,容量为无穷大,同理汇点也一样

2.结点容量:拆点,把结点拆成两个,一个入点,一个出点,并且建边,容量为结点的容量。

3.无源无汇的有容量下界的网络的最大流:详见白书,自己也不是很清楚,通过一道题懂得,在kuangbin带你飞专题里面。

4.费用和流量平方成正比的最小流:拆边法,详见大白书。

技巧:

1.二分图带权最大独立集。

2.公平分配问题。

3.区间k覆盖问题

4.最大闭合子图。

5.最大密度子图

以上的内容都是来源于白书,我感觉都是一些很概念性的东西,如果没做题的话就不能体会,所以还是要多做题!!!!

好了,下面就是牛逼的部分,附上kuangbin带你飞网络流专题的习题和详解。


kuangbin带你飞网络流专题
POJ3436——建图和最大流路径记录题解
POJ3281——巧妙建图!!题解
POJ1087——建图+建图+最大匹配(最大流)题解
poj2195——最小费用最大流模板题题解
POJ2516——分治+最小费用最大流题解
POJ1459——最大流模板题(EK,多方法)题解
hdu4280——最大流基础题题解
hdu4292——建模题(EK会超时)题解
hdu4289——最大流最小割+拆点题解
UVA10480——最大流最小割(路径输出)板子题题解
hdu2732——建图最大流+拆点题解
hdu3338——建图+最大流题解
hdu3081——最大流(二分图)+并查集(floyd)+二分题解
hdu3416——最短路+最大流题解

 

 

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值