网络最大流:
P3376 【模板】网络最大流
题目描述
如题,给出一个网络图,以及其源点和汇点,求出其网络最大流。
输入格式:
第一行包含四个正整数 N、M、S、T N 、 M 、 S 、 T ,分别表示点的个数、有向边的个数、源点序号、汇点序号。
接下来 M M 行每行包含三个正整数,表示第 i i 条有向边从出发,到达 vi v i ,边权为 wi w i (即该边最大流量为 wi w i )
输出格式:
一行,包含一个正整数,即为该网络的最大流。
输入样例#1
4 5 4 3
4 2 30
4 3 20
2 3 20
2 1 30
1 3 40
输出样例#1
50
说明
时空限制:1000ms,128M
数据规模:
对于30%的数据: N≤10,M≤25 N ≤ 10 , M ≤ 25
对于70%的数据: N≤200,M≤1000 N ≤ 200 , M ≤ 1000
对于100%的数据: N≤10000,M≤100000 N ≤ 10000 , M ≤ 100000
样例说明:
题目中存在3条路径:
4–>2–>3,该路线可通过20的流量
4–>3,可通过20的流量
4–>2–>1–>3,可通过10的流量(边4–>2之前已经耗费了20的流量)
故流量总计20+20+10=50。输出50。
网络流就是解决这一类题目,要理解网络流算法还是有一点难,我们一步一步讲。
首先比较容易想到的就是我找一条可行的边,然后把它每条边全部扣掉可行的流量,在答案里加上这个流量。可以说这样做是对的,而且这是网络流的核心思想就是这个。但是单单这样是不行的,因为这样会wa的
第一行就是我们刚刚的算法,可以发现这样的答案是20,但是正确答案是25。我们发现这样会漏掉一些答案。于是我们考虑在增广之后对这条边连一条反向边。但是这样做为什么是对的呢?其实这相当于一个撤销操作,我走逆流边,相当于把以前走过这条边的流量又推回去了。我们慢慢理解,再画一个图:
大概就是这样,而中间正反两边都走了就相当于没有走。
但是这样我们会发现一个问题:
假如我们每次都增广中间那一条,那我们要增广1e9次,这样就爆炸对不对?
那我们可能需要改进这种方法。我们每次跑一边bfs,把你的图分层,我每次只增广这个点下一层的点。这样就避免了一条路多次增广的情况。
那么我们贴一波代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
struct lxy{
int flow,next,to;
}b[200005];
int head[10005];
int cnt=-1;
int n,m,t;
int layer[10005];//分层
int cir[10005];
void add(int op,int ed,int flow)
{
cnt++;
b[cnt].next=head[op];
b[cnt].to=ed;
b[cnt].flow=flow;
head[op]=cnt;
}
bool bfs(int s)//分层,只走流量不为0的边
{
memset(layer,0,sizeof(layer));
queue <int> d;
layer[s]=1;
d.push(s);
while(!d.empty())
{
int noww=d.front();
d.pop();
for(int i=head[noww];i!=-1;i=b[i].next)
if(layer[b[i].to]==0&&b[i].flow!=0)
{
layer[b[i].to]=layer[noww]+1;
d.push(b[i].to);
}
}
return layer[t];
}
int dfs(int u,int a)//增广,a代表还能增广多少流量
{
if(u==t||a==0) return a;
int f=0;
int flow=0;//当前增广了多少流量
for(int i=cir[u];i!=-1;i=b[i].next)
{
if(layer[u]+1==layer[b[i].to]&&(f=dfs(b[i].to,min(a,b[i].flow)))>0)//f代表从这条路下去能增广多少
{
b[i].flow-=f;//边减去增广流量
b[i^1].flow+=f;//逆流边加上流量
flow+=f;//已经总增广了这么多
a=a-f;//还能增广的要减去
if(a==0)//不能增广就break
break;
}
}
return flow;
}
int dinic(int s)
{
int ans=0;
while(bfs(s))//跑一边bfs,跑一边dfs,无法到达终点就退出
{
memset(cir,0,sizeof(cir));
ans=ans+dfs(s,0x7f7f7f7f);
}
return ans;
}
int main()
{
int s;
scanf("%d%d%d%d",&n,&m,&s,&t);
for(int i=1;i<=n;i++)
head[i]=-1;
for(int i=1;i<=m;i++)//连边,虽然说走过才连逆流边,但是这里我们先把所有逆流边连好,流量为0。而且注意cnt从-1开始,这样每一条边的编号取反就是逆流边
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
add(y,x,0);
}
printf("%d",dinic(s));
}
这样我们就把这道题A了
最小割:最大流等于最小割
最小费用最大流:在最大流的基础上,我们对于每一条边流过单位流量定义一个价格,我们要求在保证最大流的情况下,花费的价格最小,求最大流和最小花费。
在学会最大流之后,这道题就变得非常简单了:我们用单位价格作为边权,逆流边边权为负,求最短路,每次增广最短路就行了。
日常贴代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<cmath>
using namespace std;
struct lxy{
int to,next,flow,w;
}b[100005];
int n,m,s,t,cnt=-1,wn;
int head[5005];
bool vis[5005];
int dis[5005];
void add(int op,int ed,int flow,int w)
{
cnt++;
b[cnt].next=head[op];
b[cnt].to=ed;
b[cnt].flow=flow;
b[cnt].w=w;
head[op]=cnt;
}
bool spfa(int s,int t)
{
memset(dis,0x3f,sizeof(dis));
memset(vis,0,sizeof(vis));
queue <int> d;
d.push(s);
vis[s]=1;dis[s]=0;
while(!d.empty())
{
int now=d.front();
d.pop();vis[now]=0;
for(int i=head[now];i!=-1;i=b[i].next)
if(b[i].flow!=0&&dis[b[i].to]>dis[now]+b[i].w)
{
dis[b[i].to]=dis[now]+b[i].w;
if(vis[b[i].to]==0)
d.push(b[i].to),vis[b[i].to]=1;
}
}
if(dis[t]==0x3f3f3f3f) return false;
return true;
}
int dfs(int u,int a,int t)//唯一一个值得注意的是,我们要记录一个vis,一次增广不能重复走一个点,否则就死循环了(连逆流边就两个自然形成0权环)。
{
vis[u]=1;
if(u==t||a==0) return a;
int k=0,f;
for(int i=head[u];i!=-1;i=b[i].next)
if(dis[u]+b[i].w==dis[b[i].to]&&b[i].flow!=0&&vis[b[i].to]==0)
{
f=dfs(b[i].to,min(a,b[i].flow),t);
b[i].flow-=f;
b[i^1].flow+=f;
k+=f;a-=f;
wn+=f*b[i].w;
if(a==0) break;
}
vis[u]=0;
return k;
}
int cost_flow(int s,int t)
{
int ans=0;
while(spfa(s,t))
memset(vis,0,sizeof(vis)),ans+=dfs(s,0x3f3f3f3f,t);
return ans;
}
int main()
{
scanf("%d%d%d%d",&n,&m,&s,&t);
for(int i=1;i<=n;i++) head[i]=-1;
for(int i=1;i<=m;i++)
{
int x,y,z,e;
scanf("%d%d%d%d",&x,&y,&z,&e);
add(x,y,z,e);
add(y,x,0,-e);
}
printf("%d ",cost_flow(s,t));
printf("%d",wn);
}
所以这两个板子有什么卵用么,感觉很板啊?
其实网络流题做多了,你就会体会到被网络流支配的恐惧。
网络流的应用一般用于二分三分四分以及n分图的匹配问题,比如说这道:P3386 【模板】二分图匹配
题目背景
二分图
题目描述
给定一个二分图,结点个数分别为
n,m
n
,
m
,边数为
e
e
,求二分图最大匹配数
输入输出格式
输入格式:
第一行,
第二至 e+1 e + 1 行,每行两个正整数 u,v u , v ,表示 u,v u , v 有一条连边
输出格式:
共一行,二分图最大匹配
输入输出样例
输入样例#1: 复制
1 1 1
1 1
输出样例#1: 复制
1
说明
n,m≤1000,1≤u≤n,1≤v≤m
n
,
m
≤
1000
,
1
≤
u
≤
n
,
1
≤
v
≤
m
因为数据有坑,可能会遇到 v>m v > m 的情况。请把 v>m v > m 的数据自觉过滤掉。
算法:二分图匹配
就是两边点的配对问题,这是最大流思想。
另外还有用最小割思想的,比如每个可以做两种选择,就可以连两种边,割一条边就等于做一种选择。然后根据题目条件连一连边,跑一下网络流,就可以得到答案。
因为网络流的脑洞都特别大,所以有时根本看不出来是网络流,而且就算看出是网络流,要把各种情况转化成连边也很难。所以网络流的难点不在跑,而在建图。