最近一段时间再搞网络流,今天终于搞完了!!!!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带你飞网络流专题的习题和详解。