网络流

网络流

网络:有向图,有一个入度为0的点,有一个出度为0的点

源点:入度为0的点s

汇点:出度为0的点t

容量:每条边的权值c(u,v)=3

流量:f(s,a)<=c(u,v)(容量限制(Capacity Constraint):对所有顶点对 u, v ∈ V,要求 f(u, v) ≤ c(u, v)。)

反对称性(Skew Symmetry):对所有顶点对 u, v ∈ V,要求 f(u, v) = - f(v, u)。

流入量:对于c来说 f(a,c)+f(b,c)

|| 流守恒性:任意u,存在 ∑ f(i,u)= ∑ f(u,j)

流出量:对于c来说 f(c,t)

总流量:|f|= ∑ f(s,v)(与s直接相连的点)

最小流:一定是0

最大流:着重讨论的问题: 给出源点 s 和汇点 t 的流网络 G,希望找出从 s 到 t 的最大值流。

割:断开后可以使s与t不连通的边的集合 |cut|=割的边值之和

最大割:等于全部边值之和

最小割:着重要讨论的问题

费用流:在最大流的情况下讨论费用

残留网络: 残留量(c'(u,v)=c(u,v)-f(u,v))组成的网络

增广路:能够增加流量的路(s-t)

限制条件: 经过边的流不能超过边的容量;
除了源点 s 和汇点 t,对于其它所有顶点,流入量与流出量要相等。

FF算法

Ford-Fulkerson 算法( 即增广路方法 简称FF方法 )是一种解决最大流的方法,其依赖于三种重要思想:
残留网络(Residual networks)
增广路径(Augmenting paths)
割(Cut)

网络达到最大流当且仅当残留网络中没有增广路

HDU - 1532 https://vjudge.net/problem/HDU-1532

#include <queue>
#include <cstring>
#include <cstdio>
#include <iostream>
using namespace std;
#define maxn 205
#define INF 1e9
//最大流板子
int n,m,a,b,cost,map[maxn][maxn];//存储网络
int max_flow(int num,int map[][maxn],int s,int t)//最大流函数(结点数,图,源点,汇点)
{
    int p[maxn],min_flow[maxn],flow[maxn][maxn],ans = 0 ;//记录结点的父节点  当前路径中最小的一段的值(限制值)  记录当前网络中的流  结果
    memset(flow,0,sizeof(flow));
    while(1)//一直循环,直到不存在增广路径
    {
        queue<int> q;
        memset(p,-1,sizeof(p));
        q.push(s);
        p[s] = -2;//源点父节点
        min_flow[s] = INF;
        while(!q.empty())//BFS   寻找增广路径
        {
            int temp = q.front();//出列
			q.pop();
            for(int i = 0 ; i < num ; i++)//从temp往外扩展
            {
                if(p[i] == -1 && flow[temp][i] < map[temp][i])
						//当结点i还未被探索到且还有可用流量
                {
                    q.push(i);//加入队列
                    p[i] = temp;//父节点
                    min_flow[i] = min(min_flow[temp],map[temp][i]-flow[temp][i]);
                }
            }
            if(p[t]!=-1)//t的父节点不=初始值,说明已经BFS到了一条路径
            {
                int k=t;
                while(p[k] >= 0)
                {flow[p[k]][k] +=min_flow[t]; flow[k][p[k]] -=min_flow[t];k = p[k];}//新流量加入flow
                break;
            }
        }
        if(p[t] != -1)ans+=min_flow[t];
        else return ans;//若不存在增广路径则返回
    }
}
int main()
{
    //n流通数 m节点数
    while(cin>>n>>m)
    {
        memset(map,0,sizeof(map));//清空
        for(int i=0;i<n;i++)
        {
			cin>>a>>b>>cost;
            map[a-1][b-1]+=cost;//存图
        }
        cout<<max_flow(m,map,0,m-1)<<endl;
    }
    return 0;
}

EK算法

Edmonds-Karp算法 即最短路径增广算法 简称EK算法

在原先每条边的基础上,加上相同容量的反向边

BFS找增广路:找到:更新最大流+更新参与网络

​ 找不到:退出

不断的找最短路 找的方法就是每次找一条边数最少的增广 也就是最短路径增广

HDU - 1532 https://vjudge.net/problem/HDU-1532

#include<iostream>
#include<algorithm>
#include<queue>
#include<cstdlib>
#include<stdio.h>
#include<cstring>
using namespace std;
const int maxn=220;
const int inf=1e9;
//EK算法
int n,m,mp[maxn][maxn],flow[maxn],pre[maxn];//n点数 m边数 map存图 flow表示当前流量 pre存储前一条增广路
queue<int> q;//因为要用BFS所以需要队列

int bfs(int s,int t)
{
    while(!q.empty())q.pop();
    memset(pre,-1,sizeof(pre));
    pre[s]=0;flow[s]=inf;
    q.push(s);
    while (!q.empty())
    {
        int p=q.front();q.pop();
        if(p==t)//走到汇点
            break;
        for(int i=1;i<=n;i++)//找边
        {

            if(i!=s&&mp[p][i]>0&&pre[i]==-1)
            {
                pre[i]=p;
                flow[i]=min(flow[p],mp[p][i]);
                q.push(i);
            }
        }
    }
    if(pre[t]==-1)return -1;
    return flow[t];//流量是增加的差值
}

int EK(int s,int t)
{
    int delta=0,tol=0;
    while (1)
    {
        delta=bfs(s,t);
        if(delta==-1)break;
        int p=t;
        while(p!=s)
        {
            mp[pre[p]][p]-=delta;
            mp[p][pre[p]]+=delta;
            p=pre[p];
        }
        tol+=delta;
    }
    return tol;
}

int main()
{
    while(cin>>m>>n)
    {
        memset(mp,0,sizeof(mp));
        memset(flow,0,sizeof(flow));
        for(int i=0;i<m;i++)
        {
            int u,v,w;cin>>u>>v>>w;
            mp[u][v]+=w;
        }
        cout<<EK(1,n)<<endl;
    }
   return 0;
}

最小费用最大流问题

通过EK,Dinic,ISAP算法可以得到网络流图中的最大流,一个网络流图中最大流的流量max_flow是唯一的,但是达到最大流量max_flow时每条边上的流量分配f是不唯一的。
如果给网络流图中的每条边都设置一个费用cost,表示单位流量流经该边时会导致花费cost。那么在这些流量均为max_flow的流量分配f中,存在一个流量总花费最小的最大流方案。
min{sum(cost(i, j)*f(i,j) | (i, j)属于方案f中的边, f(i,j)为 边(i,j)上的流量, f为某一个最大流方案}。此即为最小费用最大流

最小费用最大流问题与算法实现(Bellman-Ford、SPFA、Dijkstra)

例题:

POJ - 2135 https://vjudge.net/problem/POJ-2135

#include<stdio.h>
#include<string.h>
#include<queue>
#include<set>
#include<iostream>
#include<map>
#include<stack>
#include<cmath>
#include<algorithm>
#define ll long long
#define mod 1000000007
#define eps 1e-8
using namespace std;
const int MAXN = 10000;
const int inf = 0x3f3f3f3f;
struct Edge {
	int to,next,cap,flow,cost;
} edge[100000];
int N,head[MAXN],tol,pre[MAXN],dis[MAXN];
bool vis[MAXN];
void adde(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+1; 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;
}

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;
		if(flow==2)
		return flow;
	}
	return flow;
}
int main()
{
	int n,m;
	cin>>n>>m;
	N = n,tol = 0;
	memset(head,-1,sizeof(head));
	for(int i=0;i<m;i++)
	{
		int u,v,w;
		cin>>u>>v>>w;
		adde(u,v,1,w);
		adde(v,u,1,w);
	}
	int c;
	mincostmaxflow(1,n,c);
	cout<<c<<endl;
	return 0;
}

//最大费用最小流只要在添加边的时候换一下位置就好了
//求最大费用最大流只需要把费用换成相反数,用最小费用最大流求解即可

posted @ 2020-08-01 18:35  神奇周一  阅读(118)  评论(0编辑  收藏  举报