一.网络流:流&网络&割
1.网络流问题(NetWork Flow Problem):
给定指定的一个有向图,其中有两个特殊的点源S(Sources)和汇T(Sinks),每条边有指定的容量(Capacity),求满足条件的从S到T的最大流(MaxFlow).
下面给出一个通俗点的解释
(下文基本避开形式化的证明 基本都用此类描述叙述)
好比你家是汇 自来水厂(有需要的同学可以把自来水厂当成银行之类 以下类似)是源
然后自来水厂和你家之间修了很多条水管子接在一起 水管子规格不一 有的容量大 有的容量小
然后问自来水厂开闸放水 你家收到水的最大流量是多少
如果自来水厂停水了 你家那的流量就是0 当然不是最大的流量
但是你给自来水厂交了100w美金 自来水厂拼命水管里通水 但是你家的流量也就那么多不变了 这时就达到了最大流
2.三个基本的性质:
如果 C代表每条边的容量 F代表每条边的流量
一个显然的实事是F小于等于C 不然水管子就爆了,这就是网络流的第一条性质 容量限制(Capacity Constraints):F<x,y> ≤ C<x,y>
再考虑节点任意一个节点 流入量总是等于流出的量 否则就会蓄水(爆炸危险...)或者平白无故多出水(有地下水涌出?)
这是第二条性质 流量守恒(Flow Conservation):Σ F<v,x> = Σ F<x,u>
当然源和汇不用满足流量守恒 我们不用去关心自来水厂的水是河里的 还是江里的
最后一个不是很显然的性质 是斜对称性(Skew Symmetry): F<x,y> = - F<y,x>
这其实是完善的网络流理论不可缺少的 就好比中学物理里用正负数来定义一维的位移一样
百米起点到百米终点的位移是100m的话 那么终点到起点的位移就是-100m
同样的 x向y流了F的流 y就向x流了-F的流
对于任意一个时刻,设f(u,v)实际流量,则整个图G的流网络满足3个性质:
1. 容量限制:对任意u,v∈V,f(u,v)≤c(u,v)。
2. 反对称性:对任意u,v∈V,f(u,v) = -f(v,u)。从u到v的流量一定是从v到u的流量的相反值。
3. 流守恒性:对任意u,若u不为S或T,一定有∑f(u,v)=0,(u,v)∈E。即u到相邻节点的流量之和为0,因为流入u的流量和u点流出的流量相等,u点本身不会"制造"和"消耗"流量。
残留网络往往概括了容量网络和流量网络 是最为常用的
残留网络=容量网络-流量网络
Dinic 算法
算法思想
DINIC 在找增广路的时候也是找的最短增广路, 与 EK 算法不同的是 DINIC 算法并不是每次 bfs 只找一个增广路, 他会首先通过一次 bfs 为所有点添加一个标号, 构成一个层次图, 然后在层次图中寻找增广路进行更新。
算法流程
- 利用 BFS 对原来的图进行分层,即对每个结点进行标号, 这个标号的含义是当前结点距离源点的最短距离(假设每条边的距离都为1),注意:构建层次图的时候所走的边的残余流量必须大于0
- 用 DFS 寻找一条从源点到汇点的增广路, 注意: 此处寻找增广路的时候要按照层次图的顺序, 即如果将边(u, v)纳入这条增广路的话必须满足, 其中 u,v为结点的编号。找到一条路后要根据这条增广路径上的所有边的残余流量的最小值更新所有边的残余流量(即正向弧 - l, 反向弧 + l).
- 重复步骤 2, 当找不到一条增广路的时候, 重复步骤 1, 重新建立层次图, 直到从源点不能到达汇点为止。
算法流程如下图所示:
模板 非邻接表
#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <cmath>
#include <math.h>
#include <cstring>
#include <string>
#include <queue>
#include <deque>
#include <stack>
#include <stdlib.h>
#include <list>
#include <map>
#include <utility>
#include <time.h>
#include <set>
#include <bitset>
#include <vector>
#define pi acos(-1.0)
#define inf 0x3f3f3f3f
#define linf 0x3f3f3f3f3f3f3f3f
#define ms(a,b) memset(a,b,sizeof(a))
#define INF 0x3f3f3f3f
#define ll long long
const int maxn=1e5+5;
using namespace std;
int n,m,k;
int dep[300];
int mmp[300][300];
int cur[300];
int bfs(int s,int t)
{
ms(dep,-1);
dep[s]=0;
queue<int> q;
q.push(s);
while(!q.empty())
{
int u=q.front();q.pop();
if(u==t)return 1;
for(int i=0;i<=n;i++)
{
if(dep[i]<0 && mmp[u][i])
{
dep[i]=dep[u]+1;
q.push(i);
}
}
}
if(dep[t]<0)return 0;
return 1;
}
int dfs(int s,int t,int low=INF)
{
if(s==t)return low;
int cap;
for(int &v=cur[s];v<=n;v++)
{
if(mmp[s][v] && dep[v]==dep[s]+1 && (cap=dfs(v,t,min(low,mmp[s][v]))))
{
mmp[s][v]-=cap;
mmp[v][s]+=cap;
return cap;
}
}
return 0;
}
int dinic(int s,int t)
{
int temp,ans=0;
while(bfs(s,t))
{
for(int i=1;i<=n;i++)cur[i]=1;
ans+=dfs(s,t);
}
return ans;
}
邻接表
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <queue>
#define mset(a,i) memset(a,i,sizeof(a))
using namespace std;
typedef long long ll;
const int INF=0x3f3f3f3f;
const int MAX=1e6+5;
const int N=250;
int n,m,k;
struct node{
ll t,cap,flow,next; //cap容量,flow流量
}e[N];
int head[N],cur[N],cnt; //cur优化dfs中的head
void add(int u,int v,int cap) //u->v容量为cap
{
e[cnt].t=v;
e[cnt].cap=cap;
e[cnt].flow=0;
e[cnt].next=head[u];
//e[cnt]=node{v,cap,0,head[u]};
head[u]=cnt++;
//e[cnt]=node{u,0,0,head[v]};
e[cnt].t=u;//容量为0的反向边
e[cnt].cap=0;
e[cnt].flow=0;
e[cnt].next=head[v];
head[v]=cnt++;
}
int d[N]; //bfs深度
bool bfs(int s,int t) //O(n+m)
{
memset(d,0,sizeof(d));
queue<int>q;
q.push(s);
d[s]=1;
while(!q.empty())
{
int u=q.front();q.pop();
for(int i=head[u];~i;i=e[i].next)
{
int v=e[i].t;
if(d[v]==0&&e[i].cap-e[i].flow>0)
{
d[v]=d[u]+1;
q.push(v);
}
}
}
return d[t]>0; //存在增广路
}
ll dfs(int s,int t,ll minedge)
{
if(s==t)return minedge;
ll flow=0; //从当前s点流出的流量
for(int &i=cur[s];~i;i=e[i].next)
{
int v=e[i].t;
if(d[v]==d[s]+1&&e[i].cap-e[i].flow>0) //层次关系&&有剩余流量
{
ll temp=dfs(v,t,min(minedge-flow,e[i].cap-e[i].flow));
e[i].flow+=temp; //流量增加
e[i^1].flow-=temp; //反向边流量减少
flow+=temp; //flow已分配的流量
if(flow==minedge)return flow; //已达到祖先的最大流,无法再大,剪枝
}
}
if(flow==0)d[s]=0; //此点已无流,标记掉
return flow;
}
ll dinic(int s,int t) //一定要建立反向边cap=0
{
ll maxflow=0;
while(bfs(s,t)) //有增广路
{
memcpy(cur,head,sizeof(head)); //重要的优化
maxflow+=dfs(s,t,INF);
}
return maxflow;
}
网上的板子
#include<bits/stdc++.h>
#define LL long long
#define M(a,b) memset(a,b,sizeof a)
#define pb(x) push_back(x)
using namespace std;
const int maxn=1000005;
const int inf=0x3f3f3f3f;
struct edge
{
int to,val,nxt;
edge(){}
edge(int a,int b,int c)
{
to=a,val=b,nxt=c;
}
}mp[maxn*10];
int head[maxn],cnt;
int n,m,s,t;
struct node
{
int from,to,val;
}a[maxn];
void addedge(int from,int to,int val)
{
mp[cnt]=edge(to,val,head[from]);
head[from]=cnt++;
mp[cnt]=edge(from,0,head[to]);
head[to]=cnt++;
}
int dep[maxn];
bool bfs(int st,int ed)
{
M(dep,-1);
queue<int>q;
while(!q.empty())q.pop();
dep[st]=0;
q.push(st);
while(!q.empty())
{
int tmp=q.front();
q.pop();
if(tmp==ed) return true;
for(int i=head[tmp]; i!=-1; i=mp[i].nxt)
{
int &to=mp[i].to,flow=mp[i].val;
if(dep[to]==-1&&flow)
{
dep[to]=dep[tmp]+1;
q.push(to);
if(to==ed)return true;
}
}
}
return false;
}
int cur[maxn];
int dfs(int s,int t,int flow)
{
if(s==t||flow==0)return flow;
int pre=0;
for(int &i=cur[s]; i!=-1; i=mp[i].nxt)
{
int &to=mp[i].to,val=mp[i].val;
if(dep[s]+1==dep[to]&&val)
{
int tmp=min(flow-pre,val);
int sub=dfs(to,t,tmp);
mp[i].val-=sub;
mp[i^1].val+=sub;
pre+=sub;
if(pre==flow)return pre;
}
}
return pre;
}
int dinic(int st,int ed)
{
int ans=0;
while(bfs(st,ed))
{
for(int i=1;i<=n+m+2;i++)cur[i]=head[i];
ans+=dfs(st,ed,inf);
}
return ans;
}
int init()
{
M(head,-1);
cnt=0;
}
int main()
{
scanf("%d%d%d%d",&n,&m,&s,&t);
init();
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&a[i].from,&a[i].to,&a[i].val);
addedge(a[i].from,a[i].to,a[i].val);
}
int ans=dinic(s,t);
printf("%d\n",ans);
}