网络流 :
是指在一个每条边都有容量的有向图分配流,使一条边的流量不会超过它的容量。通常在运筹学中,有向图称为网络。顶点称为节点而边称为弧。一道流必须匹配一个结点的进出的流量相同的限制,除非这是一个源点──有较多向外的流,或是一个汇点──有较多向内的流。一个网络可以用来模拟道路系统的交通量、管中的液体、电路中的电流或类似一些东西在一个结点的网络中游动的任何事物。
算法案例引入 :
然后自来水厂和你家之间修了很多条水管子接在一起 水管子规格不一 有的容量大 有的容量小
然后问自来水厂开闸放水 你家收到水的最大流量是多少
明确概念 :
容量:每条边都有一个容量(水管的最大水流容量)
源点:出发点(水厂)。
汇点:结束点(废水站)。
流:一个合法解称作一个流,也就是一条可以从源点到汇点的一条合法路径。
流量:每条边各自被经过的次数称作其流量,最终收集的总数为整个流的流量。
算法分析 :
容量限制:每条边的流量不超过其容量(水管会爆的)。
流量平衡:对于除源点和汇点以外的点来说,其流入量一定等于流出量。
现在,先简化一下这个图,来解决这个问题。
x/y表示总流量为y,已经流了x.
首先想到,随机找路径,然而如果走到如上图所示。
当走完,1->2->3->4就找不到其他路径了,那么答案为1吗?不,答案为2.
首先来手算一下,很容易可以得出这个最大流是走(1,2,4)和(1,3,4)得到的2,即最大流为2
现在改进算法,给流过的路径建反向边,像这样:
给程序有反悔的机会。
定义一跳变得残量为:容量 - 已流过的流量。
反向边的流量值=正向流过的总流量,也就是说正向流过多少,反向可以流回多少。
从而又找到1->3->2->4的一条路径。
再次建路径上的反向边,发现没有路径可以到达4点,所以答案为2.
最大流最小割定理 :
最大流最小割定理提供了对于一个网络流,从源点到目标点的最大的流量等于最小割的每一条边的和。
这个定理说明,当网络达到最大流时,会有一个割集,这个割集中的所有边都达到饱和状态。
这等价于在网络中再也找不到一个从s到t的增广路径。
因为只要能找到一条增广路径,这条增广路径肯定要经过最小割集中的一条边,否则这个割集就不能称之为割集了。
既然这个割集中所有的边都饱和了,因此也就不会存在这样的增广路径了。
这个定理的意义在于给我们指明了方向:
任何算法,只要最后能达到“再也找不到一条增广路径”,就可以说明这个算法最后达到了最大流。
小结 :
总结一下上面求最大流的步骤:
1.在图上找到一条从源点到汇点的路径(称为‘增广路’)。
2.去增广路上的残量最小值v。(也就是流过的路径中流量最小的那一个)
3.将答案加上v。
4,.将增广路上所有边的残量减去v,反向边的残量加上v。
重复上边4个步骤直到找不到增光路为止
最大流/最小割(Dinic算法)
如果增广一次后发现最短路没有变化,那么可以继续增广,直到源点到汇点的增广路增大,才需要一边bfs。
bfs之后我们去除那些可能在最短路上的边,即dis[终点]=dis[起点]+1的那些边。
显然这些边构成的图中没有环。
只需要延这些边尽可能的增广即可。
bfs处
bool bfs(int s,int t){
for(int i=0;i<=n+1;i++)deep[i]=inf;
for(int i=1;i<=n;i++)cur[i]=head[i];
deep[s]=0;
q.push(s);
while(!q.empty()){
int from=q.front();
q.pop();
for(int i=head[from];i!=-1;i=e[i].next){
int to=e[i].to;
if(deep[to]==inf&&e[i].w){
deep[to]=deep[from]+1;
q.push(to);
}
}
}
if(deep[t]<inf)return 1;
else return 0;
}
当图联通时进行dfs,目前节点为u,每次经过与u距离最近的点,并且这条边的残量值要大于0,然后往后进行dfs。
我们在dfs是要加一个变量,作为流量控制(后边的流量不能超过前边流量的最小值)。
dfs中变量flow记录这条管道之后的最大流量。
dfs处 :
int dfs(int from,int t,int limit){
if(!limit||from==t)return limit;
int flow=0,f;
for(int i=cur[from];i!=-1;i=e[i].next){
cur[from]=i;
int to=e[i].to;
if(deep[to]==deep[from]+1&&(f=dfs(to,t,min(limit,e[i].w)))){
flow+=f;
limit-=f;
e[i].w-=f;
e[i^1].w+=f;
if(!limit)break;
}
}
return flow;
}
重复上边如果图联通(有最短路径),就一直进行增广。
while(bfs())ans+=dfs(S,inf);
1.Dinic O(n2 * m) n为点数 m为边数 代码里加入了当前弧优化
完整代码 :
#include<bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
const int N=3020000;
int s,t,n,m,x,y,z,maxflow,deep[N];
struct Edge{
int next,to,w;
}e[N];
int cnt=-1,head[N],cur[N];
queue<int >q;
void add(int from,int to,int w){
e[++cnt].next=head[from];
e[cnt].to=to;
e[cnt].w=w;
head[from]=cnt;
}
bool bfs(int s,int t){
for(int i=0;i<=n+1;i++)deep[i]=inf;
for(int i=1;i<=n;i++)cur[i]=head[i];
deep[s]=0;
q.push(s);
while(!q.empty()){
int from=q.front();
q.pop();
for(int i=head[from];i!=-1;i=e[i].next){
int to=e[i].to;
if(deep[to]==inf&&e[i].w){
deep[to]=deep[from]+1;
q.push(to);
}
}
}
if(deep[t]<inf)return 1;
else return 0;
}
int dfs(int from,int t,int limit){
if(!limit||from==t)return limit;
int flow=0,f;
for(int i=cur[from];i!=-1;i=e[i].next){
cur[from]=i;
int to=e[i].to;
if(deep[to]==deep[from]+1&&(f=dfs(to,t,min(limit,e[i].w)))){
flow+=f;
limit-=f;
e[i].w-=f;
e[i^1].w+=f;
if(!limit)break;
}
}
return flow;
}
void dinic(int s,int t){
while(bfs(s,t)){
maxflow+=dfs(s,t,inf);
}
}
int main(){
memset(head,-1,sizeof head);
scanf("%d%d%d%d",&n,&m,&s,&t);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
add(y,x,0);
}
dinic(s,t);
printf("%d",maxflow);
return 0;
}
Dinic
#include <cstdio>
#include <string.h>
#include <queue>
using namespace std;
int const inf = 0x3f3f3f3f;
int const MAX = 205;
int n, m;
int c[MAX][MAX], dep[MAX];//dep[MAX]代表当前层数
int bfs(int s, int t)//重新建图,按层次建图
{
queue<int> q;
while(!q.empty())
q.pop();
memset(dep, -1, sizeof(dep));
dep[s] = 0;
q.push(s);
while(!q.empty()){
int u = q.front();
q.pop();
for(int v = 1; v <= m; v++){
if(c[u][v] > 0 && dep[v] == -1){//如果可以到达且还没有访问,可以到达的条件是剩余容量大于0,没有访问的条件是当前层数还未知
dep[v] = dep[u] + 1;
q.push(v);
}
}
}
return dep[t] != -1;
}
int dfs(int u, int mi, int t)//查找路径上的最小流量
{
if(u == t)
return mi;
int tmp;
for(int v = 1; v <= m; v++){
if(c[u][v] > 0 && dep[v] == dep[u] + 1 && (tmp = dfs(v, min(mi, c[u][v]), t))){
c[u][v] -= tmp;
c[v][u] += tmp;
return tmp;
}
}
return 0;
}
int dinic()
{
int ans = 0, tmp;
while(bfs(1, m)){
while(1){
tmp = dfs(1, inf, m);
if(tmp == 0)
break;
ans += tmp;
}
}
return ans;
}
int main()
{
while(~scanf("%d %d", &n, &m)){
memset(c, 0, sizeof(c));
int u, v, w;
while(n--){
scanf("%d %d %d", &u, &v, &w);
c[u][v] += w;
}
printf("%d\n", dinic());
}
return 0;
}
最大流/最小割(ISAP算法)
ISAP 理论时间复杂度与Dinic相同 但二分图时ISAP更有优势
(另外由于ISAP预处理出来了大致的最大流路径 故当网络流中途带修时ISAP需要重新做bfs 因此优先考虑其他网络流算法)
#include<bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
const int N=3020000;
int s,t,n,m,x,y,z,maxflow,deep[N],lay[N];
struct Edge{
int next,to,w;
}e[N];
int cnt=-1,head[N],cur[N];
queue<int >q;
void add(int from,int to,int w){
e[++cnt].next=head[from];
e[cnt].to=to;
e[cnt].w=w;
head[from]=cnt;
}
void bfs(int s,int t){
for(int i=0;i<=n+1;i++){
deep[i]=inf;
lay[i]=0;
}
deep[t]=0;
lay[0]=1;
q.push(t);
while(!q.empty()){
int from=q.front();
q.pop();
for(int i=head[from];i!=-1;i=e[i].next){
int to=e[i].to;
if(deep[to]==inf){
deep[to]=deep[from]+1;
lay[deep[to]]++;
q.push(to);
}
}
}
return;
}
int dfs(int from,int t,int limit){
if(from==t){
maxflow+=limit;
return limit;
}
int flow=0;
for(int i=cur[from];i!=-1;i=e[i].next){
int to=e[i].to;
cur[from]=i;
if(deep[to]+1==deep[from]&&e[i].w){
int f=dfs(to,t,min(e[i].w,limit));
flow+=f;
limit-=f;
e[i].w-=f;
e[i^1].w+=f;
if(!limit)return flow;
}
}
lay[deep[from]]--;
if(lay[deep[from]]==0)deep[s]=n+1;
deep[from]++;
lay[deep[from]]++;
return flow;
}
void ISAP(int s,int t){
bfs(s,t);
while(deep[s]<n){
for(int i=1;i<=n;i++)cur[i]=head[i];
dfs(s,t,inf);
}
}
int main(){
memset(head,-1,sizeof head);
scanf("%d%d%d%d",&n,&m,&s,&t);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
add(y,x,0);
}
ISAP(s,t);
printf("%d",maxflow);
return 0;
}
ISAP
最小费用最大流
问题 :
给出一个网络图,以及其源点和汇点,每条边已知其最大流量和单位流量费用,求出其网络最大流和在最大流情况下的最小费用。
实现 :
那么他既然要求最小花费,我们不妨把这个最小花费看成边的权值,构建一个图用最短路算法来找到源点到各个点的最短距离
找到这个数据之后,我们就可以沿着最短路来进行增广,即在最短路中求到一条可行路然后修改其残量,我们可以保证其为最大流中的一部分的最小花费
不断的进行增广直到我们找到了全部值,然后得解,这就是将dinic和spfa结合起来的求解最小费用最大流问题的方法
#include<bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
const int N=120000;
int s,t,n,m,x,y,z,d,maxflow,mincost,limit[N],bac[N],pre[N],dis[N],v[N];
struct Edge{
int next,to,w,dis;
}e[N];
int cnt=-1,head[N];
queue<int >q;
void add(int from,int to,int w,int dis){
e[++cnt].next=head[from];
e[cnt].to=to;
e[cnt].w=w;
e[cnt].dis=dis;
head[from]=cnt;
}
int spfa(int s,int t){
for(int i=1;i<=n;i++){
dis[i]=inf;
limit[i]=inf;
v[i]=0;
}
pre[t]=-1;
q.push(s);
dis[s]=0;
v[s]=1;
while(!q.empty()){
int from=q.front();
q.pop();
v[from]=0;
for(int i=head[from];i!=-1;i=e[i].next){
int to=e[i].to;
if(dis[to]>dis[from]+e[i].dis&&e[i].w>0){
dis[to]=dis[from]+e[i].dis;
pre[to]=from;
bac[to]=i;
limit[to]=min(limit[from],e[i].w);
if(!v[to]){
v[to]=1;
q.push(to);
}
}
}
}
if(pre[t]!=-1)return limit[t];
else return 0;
}
void MCMF(int s,int t){
int flow=0;
while(flow=spfa(s,t)){
int k=t;
while(k!=s){
e[bac[k]].w-=flow;
e[bac[k]^1].w+=flow;
k=pre[k];
}
mincost+=flow*dis[t];
maxflow+=flow;
}
}
int main(){
memset(head,-1,sizeof head);
scanf("%d%d%d%d",&n,&m,&s,&t);
for(int i=1;i<=m;i++){
scanf("%d%d%d%d",&x,&y,&z,&d);
add(x,y,z,d);
add(y,x,0,-d);
}
MCMF(s,t);
printf("%d %d",maxflow,mincost);
return 0;
}
//转博客
//https://www.cnblogs.com/passione-123456/p/12234799.html
上下界网络流
1.无源汇上下界可行流:
#include<bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
const int N=3020000;
int s,t,n,m,x,y,z,maxflow,deep[N],lay[N],low[N],high[N],out[N],sum;
struct Edge{
int next,to,w;
}e[N];
int cnt=-1,head[N],cur[N];
queue<int >q;
void add(int from,int to,int w){
e[++cnt].next=head[from];
e[cnt].to=to;
e[cnt].w=w;
head[from]=cnt;
}
void bfs(int s,int t){
for(int i=0;i<=n+1;i++){
deep[i]=inf;
lay[i]=0;
}
deep[t]=0;
lay[0]=1;
q.push(t);
while(!q.empty()){
int from=q.front();
q.pop();
for(int i=head[from];i!=-1;i=e[i].next){
int to=e[i].to;
if(deep[to]==inf){
deep[to]=deep[from]+1;
lay[deep[to]]++;
q.push(to);
}
}
}
return;
}
int dfs(int from,int t,int limit){
if(from==t){
maxflow+=limit;
return limit;
}
int flow=0;
for(int i=cur[from];i!=-1;i=e[i].next){
int to=e[i].to;
cur[from]=i;
if(deep[to]+1==deep[from]&&e[i].w){
int f=dfs(to,t,min(e[i].w,limit));
flow+=f;
limit-=f;
e[i].w-=f;
e[i^1].w+=f;
if(!limit)return flow;
}
}
lay[deep[from]]--;
if(lay[deep[from]]==0)deep[s]=n+1;
deep[from]++;
lay[deep[from]]++;
return flow;
}
void ISAP(int s,int t){
bfs(s,t);
while(deep[s]<n){
for(int i=1;i<=n;i++)cur[i]=head[i];
dfs(s,t,inf);
}
}
int main(){
memset(head,-1,sizeof head);
scanf("%d%d",&n,&m);
s=n+1,t=n+2;
for(int i=0;i<m;i++){//×¢Òâ m´Ó0¿ªÊ¼
scanf("%d%d%d%d",&x,&y,&low[i],&high[i]);
add(x,y,high[i]-low[i]);
add(y,x,0);
out[x]-=low[i];
out[y]+=low[i];
}
for(int i=1;i<=n;i++){
if(out[i]>0){
sum+=out[i];
add(s,i,out[i]);
add(i,s,0);
}else if(out[i]<0){
add(i,t,-out[i]);
add(t,i,0);
}
}
n=n+2;
ISAP(s,t);
if(maxflow!=sum){
printf("NO\n");
}else{
printf("YES\n");
for(int i=0;i<m;i++){
printf("%d\n",e[i*2|1].w+low[i]);
}
}
return 0;
}
ISAP
//转博客
//https://www.cnblogs.com/passione-123456/p/12234799.html
2.有源汇上下界最大/最小/可行流:
#include<bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
const int N=9000300;
int s,t,S,T,n,m,x,y,z,maxflow,deep[N],lay[N],low[N],high[N],out[N],sum;
struct Edge{
int next,to,w;
}e[N],E[N];
int cnt=-1,num,tot,head[N],cur[N],Head[N];
int ans;
queue<int >q;
void add(int from,int to,int w){
e[++cnt].next=head[from];
e[cnt].to=to;
e[cnt].w=w;
head[from]=cnt;
}
bool bfs(int s,int t){
for(int i=0;i<=n+1;i++)deep[i]=inf;
for(int i=1;i<=n;i++)cur[i]=head[i];
deep[s]=0;
q.push(s);
while(!q.empty()){
int from=q.front();
q.pop();
for(int i=head[from];i!=-1;i=e[i].next){
int to=e[i].to;
if(deep[to]==inf&&e[i].w){
deep[to]=deep[from]+1;
q.push(to);
}
}
}
if(deep[t]<inf)return 1;
else return 0;
}
int dfs(int from,int t,int limit){
if(!limit||from==t)return limit;
int flow=0,f;
for(int i=cur[from];i!=-1;i=e[i].next){
cur[from]=i;
int to=e[i].to;
if(deep[to]==deep[from]+1&&(f=dfs(to,t,min(limit,e[i].w)))){
flow+=f;
limit-=f;
e[i].w-=f;
e[i^1].w+=f;
if(!limit)break;
}
}
return flow;
}
void dinic(int s,int t){
while(bfs(s,t)){
maxflow+=dfs(s,t,inf);
}
}
int main(){
memset(head,-1,sizeof head);
scanf("%d%d%d%d",&n,&m,&S,&T);
s=n+1,t=n+2;
for(int i=0;i<m;i++){
scanf("%d%d%d%d",&x,&y,&low[i],&high[i]);
add(x,y,high[i]-low[i]);
add(y,x,0);
out[x]-=low[i];
out[y]+=low[i];
}
for(int i=1;i<=n;i++){
if(out[i]>0){
sum+=out[i];
add(s,i,out[i]);
add(i,s,0);
}else if(out[i]<0){
add(i,t,-out[i]);
add(t,i,0);
}
}
add(T,S,inf);
add(S,T,0);
n=n+2;
dinic(s,t);
s=T,t=S;
if(maxflow!=sum){//ÅжÏÊÇ·ñΪÓÐÔ´»ãÉÏϽç¿ÉÐÐÁ÷
printf("please go home to sleep\n");
}else{
ans=e[cnt].w;
e[cnt].w=0;
e[cnt^1].w=0;
s=S,t=T;
//ÒÔÏÂΪÇó³öÓÐÔ´»ãÉÏϽç×î´óÁ÷
maxflow=0;
dinic(s,t);
ans+=maxflow;
printf("%d\n",ans);
//ÒÔÏÂΪÇó³öÓÐÔ´»ãÉÏϽç×îСÁ÷
maxflow=0;
dinic(T,S);
ans-=maxflow;
printf("%d\n",ans);
}
return 0;
}
Dinic
//转博客
//https://www.cnblogs.com/passione-123456/p/12234799.html