网络流
网络流
网络:有向图,有一个入度为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;
}
//最大费用最小流只要在添加边的时候换一下位置就好了
//求最大费用最大流只需要把费用换成相反数,用最小费用最大流求解即可