网络流
一.最大流问题
1.增广路算法
1).Ford-Fulkerson算法: 残留网络中寻找增加路径
STEP0:置初始可行流。
STEP1:构造原网络的残量网络,在残量网络中找s-t有向路。如果没有,算法得到最大流结束。否则继续下一步。
STEP2:依据残量网络中的s-t有向路写出对应到原网络中的s-t增广路。对于增广路中的前向弧,置s(e)=u(e)- f(e)。对于反向弧,置s(e)=f(e)
STEP3:计算crement=min{s(e1),s(e2),…,s(ek)}
STEP4:对于增广路中的前向弧,令f(e)=f(e)+crement;对于其中的反向弧,令f(e)=f(e)-crement,转STEP1。
2).Edmonds-Korp算法
算法实现:
<span style="font-size:18px;"><strong>#include<stdio.h>
#include<string.h>
#include<queue>
#define M 0x7fffffff
#define Max 506
using namespace std;
int cap[Max][Max],f[Max][Max];
int pre[Max]; //记录增广路径
int p[Max]; //记录增广时的残量
int n,m;
int EK(int s,int t)
{
int v,u;
int sum=0;
queue<int >q;
memset(f,0,sizeof(f));
//memset(pre,0,sizeof(pre));
while(true) //BFS找增广路
{
memset(p,0,sizeof(p));
p[s]=M;
q.push(s);
while(!q.empty())
{
u=q.front();
q.pop();
for(v=1;v<=m;v++)
{
if(!p[v] && f[u][v]<cap[u][v]) //找到新节点
{
pre[v]=u; //记录v的父亲,并加入FIFO队列
q.push(v);
p[v]=p[u]<cap[u][v]-f[u][v]?p[u]:cap[u][v]-f[u][v]; //s-v路径上的最小残量
}
}
}
if(p[t]==0) break; //找不到增广路,则当前流已经是最大流(最小割最大流定理)
for(u=t;u!=s;u=pre[u])
{
f[pre[u]][u]+=p[t]; //更新正向流量
f[u][pre[u]]-=p[t]; //更新反向流量
}
sum+=p[t]; //更新从s流出的总流量
}
return sum;
}
int main ()
{
int i;
int a,b,c;
while (~scanf("%d%d",&n,&m))
{
memset(cap,0,sizeof(cap));
for(i=0;i<n;i++)
{
scanf("%d%d%d",&a,&b,&c);
cap[a][b]+=c; //输入可能相同的边
}
printf("%d\n",EK(1,m));
}
return 0;
}
</strong></span>
2.预流推进算法(Dinic算法)
步骤0:构造初始预流flow。
对源顶点s的每条出边(s,v),令flow(s,v)=cap(s,v);
对其余边(u,v),令flow(u,v)=0。构造一有效的高度函数h。
步骤1:如果残量网络中不存在活顶点,则计算结束,已经得到最大流.否则转步骤2。
步骤2:在网络中选取活顶点v。
如果存在顶点v的出边为可推流边,则选取一条这样的可推流边,并沿此边推流。
否则,令h(v) = min{h(w)+1 | (v,w)是当前残流网络中的边},并转步骤1。
算法实现:
<span style="font-size:18px;"><strong>#include <cstdio>
#include <cstring>
#include <queue>
#define MAXN 205
#define INF 1000000000
using namespace std;
struct Edge {
int from, to, cap, flow;
};
struct Dinic {
int n, m, s, t;
vector<Edge> edges; //边表.edges[e]和edges[e^1]互为反向弧
vector<int> G[MAXN]; //邻接表,G[i][j]表示结点i的第j条边在e数组中的序号
bool vis[MAXN]; //BFS使用
int d[MAXN]; //从起点到i的距离
int cur[MAXN]; //当前弧指针
void ClearAll(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);
}
bool BFS() {//使用BFS计算出每一个点在残量网络中到t的最短距离d.
memset(vis, 0, sizeof(vis));
queue<int> Q;
Q.push(s);
vis[s] = 1;
d[s] = 0;
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) { //只考虑残量网络中的弧
vis[e.to] = 1;
d[e.to] = d[x] + 1;
Q.push(e.to);
}
}
}
return vis[t];
}
int DFS(int x, int a) {//使用DFS从S出发,沿着d值严格递减的顺序进行多路增广。
if (x == t || a == 0) return a;
int flow = 0, f;
for (int& i = cur[x]; i < G[x].size(); i++) {
Edge& e = edges[G[x][i]];
if (d[x] + 1 == d[e.to] && (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 Maxflow(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;
}
};
Dinic g;
int main()
{
int n, m, i, a, b, c;
while (~scanf("%d%d", &m, &n)) {
g.ClearAll(n + 1);
for (i = 0; i < m; i++) {
scanf("%d%d%d", &a, &b, &c);
g.AddEdge(a, b, c);
}
int flow = g.Maxflow(1, n);
printf("%d\n", flow);
}
return 0;
} </strong></span>
二.最小费用最大流
求解最小费用流的步骤和求最大流的步骤几乎完全一致,只是在步骤1时选一条非饱和路时,应选代价和最小 的路,即最短路。
步骤1. 选定一条总的单位费用最小的路,即要给定最小费用的初始可行流,而不是包含边数最小的路。
步骤2. 不断重复求最大流的步骤来进行,直到没有饱和路存在为止。然后计算每个路的总费用。
求解最小费用流的步骤和求最大流的步骤几乎完全一致,只是在步骤1时选一条非饱和路时,应选代价和最小 的路,即最短路。
步骤1. 选定一条总的单位费用最小的路,即要给定最小费用的初始可行流,而不是包含边数最小的路。
步骤2. 不断重复求最大流的步骤来进行,直到没有饱和路存在为止。然后计算每个路的总费用。
算法实现
#include <iostream>
#include <queue>
using namespace std;
#define INF 1000000
const int MAXN=1000;
/*********************************/
* 最小割最大流算法
* 可以在求得最大流的同时获取最小割集S,T
* 邻接矩阵存储各类信息
/*********************************/
int cap[MAXN][MAXN]; //容量 ,没有边为0
int flow[MAXN][MAXN]; //流量
int cost[MAXN][MAXN]; //耗费矩阵
int p[MAXN]; //增广路前驱
int d[MAXN]; //s-t路径最小耗费
bool inq[MAXN]; //队列标记
int n; // 顶点数目
int f; //最大流
int s,t; //源点,汇点
int c; //最大流下最小耗费
/*********************************/
* 邻接矩阵读入图数据
* 例子:
4
0 2 1 0
0 0 1 1
0 0 0 1
0 0 0 0
0 2 5 0
-2 0 2 3
-5 -2 0 1
0 -3 -1 0
注明: 费用矩阵是对称的,有i,j的费用,则j,i费用为其相反数
/*********************************/
void read()
{
cin>>n;
for(int i=0;i<n;++i)
{
for(int j=0;j<n;++j)
{
cin>>cap[i][j];
}
}
for(int i=0;i<n;++i)
{
for(int j=0;j<n;++j)
{
cin>>cost[i][j];
}
}
}
/*********************************/
* void minFlow()
* 最小费用最大流算法
* SPFA()算法找最小耗费增广路
* SPFA()其实是Bellman的一个小变形
* 求得的f一定是最大流,求得的c是最大流下最小耗费
/*********************************/
void minFlow()
{
cin>>s>>t;
queue<int> q;
memset(flow,0,sizeof(flow));
c=f=0;
while(1)
{
memset(inq,0,sizeof(inq));
for(int i=0;i<n;++i)
{
d[i]= i==s ? 0:INF;
}
q.push(s);
while(!q.empty())
{
int u=q.front(); q.pop();
inq[u]=false;
for(int v=0;v<n;v++)
{
if(cap[u][v]-flow[u][v]>0&&d[v]>d[u]+cost[u][v])
{
d[v]=d[u]+cost[u][v];
p[v]=u;
if(!inq[v])
{
inq[v]=true;
q.push(v);
}
}
}
}
if(d[t]==INF)
{
cout<<"最大流:"<<f<<",最小耗费:"<<c<<endl;
break;
}
int a=INF;
for(int u=t;u!=s;u=p[u])
{
if(cap[p[u]][u]-flow[p[u]][u]<a)
{
a=cap[p[u]][u]-flow[p[u]][u];
}
}
for(int u=t;u!=s;u=p[u])
{
flow[p[u]][u]+=a;
flow[u][p[u]]-=a;
}
c+=d[t]*a;
f+=a;
}
}
int main()
{
readG();
minCmaxF();
return 0;
}