网络流分为两种,最大流和最小费用最大流。
关于模板
最大流
EK
Dinic
HLPP
ISAP
我只会dinic
就是bfs+dfs,在每次增广的时候用bfs进行分层,每次在增广的时候少走一些不必要的路。
#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstring>
using namespace std;
const int maxn = 10010;
const int maxm = 200100;
const int inf=1e9;
int head[maxn],k;
int n,m,s,t;
struct node{
int to;
int next;
int w;
}e[maxm];
void add(int u,int v,int w){
e[++k].next=head[u];
e[k].w=w;
e[k].to=v;
head[u]=k;
}
void build(){
k=-1;
memset(head,-1,sizeof(head));
for(int i=1;i<=m;i++){
int x,y,w;
scanf("%d%d%d",&x,&y,&w);
add(x,y,w);
add(y,x,0);
}
}
int level[maxn];
bool bfs(){
for(int i=1;i<=n;i++){
level[i]=0;
}
queue<int> q;
q.push(s);
level[s]=1;
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];i!=-1;i=e[i].next){
int v=e[i].to;
if(e[i].w>0&&!level[v]){
q.push(v);
level[v]=level[u]+1;
if(v==t) return true;
}
}
}
return false;
}
int dfs(int u,int maxflow){
if(u==t) return maxflow;
int tmp=maxflow;
for(int i=head[u];i!=-1;i=e[i].next){
int v=e[i].to,f=e[i].w;
if(f>0 && level[v]==level[u]+1){
int addf=dfs(v,min(tmp,f));
tmp-=addf;
e[i].w-=addf;
e[i^1].w+=addf;
}
}
return maxflow-tmp;
}
void dinic(){
int maxf=0;
while(bfs()){
maxf+=dfs(s,inf);
}
printf("%d\n",maxf);
}
int main(){
cin>>n>>m>>s>>t;
build();
dinic();
}
当前弧优化最大流
就是最大流的一些优化,其实优化了很多
在写这个的时候,对付大多数题是不会TLE的。
#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstring>
using namespace std;
const int maxn = 10010;
const int maxm = 300100;
const int inf=0x3f3f3f3f;
int head[maxn],k;
int n,m,s,t;
int cur[maxn];
struct node{
int to;
int next;
int w;
}e[maxm];
void add_edge(int u,int v,int w){
e[++k].next=head[u];
e[k].w=w;
e[k].to=v;
head[u]=k;
}
void add(int u,int v,int w){
add_edge(u,v,w);
add_edge(v,u,0);
}
void build(){
scanf("%d%d%d%d",&n,&m,&s,&t);
k=1;
memset(head,-1,sizeof(head));
for(int i=1;i<=m;i++){
int x,y,w;
scanf("%d%d%d",&x,&y,&w);
add(x,y,w);
}
}
int level[maxn],inq[maxn];
bool bfs(){
memset(level,0,sizeof(level));
queue<int> q;
q.push(s);
level[s]=1;
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];i!=-1;i=e[i].next){
int v=e[i].to;
if(e[i].w>0&&!level[v]){
q.push(v);
level[v]=level[u]+1;
if(v==t) return true;
}
}
}
return false;
}
int dfs(int u,int flow) {
if(u==t) return flow;
int ans=0;
for(int i=cur[u];i!=-1&&ans<flow;i=e[i].next){
cur[u]=i;
int v=e[i].to;
if(e[i].w&&level[v]==level[u]+1) {
int x=dfs(v,min(e[i].w,flow-ans));
if(x) e[i].w-=x,e[i^1].w+=x,ans+=x;
}
}
if(ans<flow) level[u]=-1;
return ans;
}
void dinic(){
int maxf=0;
while(bfs()){
memcpy(cur,head,sizeof(head));
maxf+=dfs(s,inf);
}
printf("%d\n",maxf);
}
int main(){
build();
dinic();
}
最小费用最大流
spfa+dfs(把EK中的bfs换成spfa)
#include<bits/stdc++.h>
using namespace std;
const int maxn=10010;
const int maxm=200020;
const int inf=0x3f3f3f3f;
struct node{
int next;
int to;
int w;
int flow;
}e[maxm];
int cnt=1,head[maxn];
void add_edge(int u,int v,int f,int w){
e[++cnt].next=head[u];
e[cnt].to=v;
e[cnt].w=w;
e[cnt].flow=f;
head[u]=cnt;
}
void add(int u,int v,int f,int w){
add_edge(u,v,f,w);
add_edge(v,u,0,-w);
}
int maxflow,mincost,s,t,n,m,x,y,z,f;
int dis[maxn],inq[maxn];
int pre[maxn],flow[maxn],last[maxn];
queue<int> q;
bool spfa(int s,int t){
memset(dis,inf,sizeof(dis));
memset(inq,0,sizeof(inq));
memset(flow,inf,sizeof(flow));
inq[s]=1;q.push(s);pre[t]=-1;dis[s]=0;
while(!q.empty()){
int u=q.front();
q.pop();
inq[u]=0;
for(int i=head[u];i!=-1;i=e[i].next){
int v=e[i].to;
if(dis[v]>dis[u]+e[i].w && e[i].flow>0){
dis[v]=dis[u]+e[i].w;
pre[v]=u;
last[v]=i;
flow[v]=min(flow[u],e[i].flow);
if(!inq[v]){
inq[v]=1;
q.push(v);
}
}
}
}
return pre[t]!=-1;
}
void mcmf(){
while(spfa(s,t)){
int u=t;
maxflow+=flow[t];
mincost+=flow[t]*dis[t];
while(u!=s){
e[last[u]].flow-=flow[t];
e[last[u]^1].flow+=flow[t];
u=pre[u];
}
}
}
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,&f);
add(x,y,z,f); add(y,x,0,-f);
}
mcmf();
printf("%d %d",maxflow,mincost);
return 0;
}
关于建图的一些想法
为什么这道题我要用网络流?
网络流的题目的核心在于选与不选的问题。选会发生什么,会产生多少的收益,对其他会产生什么的影响。不选又会发生什么。这样就可以体现出网络流的优点------自适应性。
网络流的核心特点在于它的适应性,就是选与不选之间的选择。这个性质可以由增广来实现,所以我们可以通过设置网络流中的一些参数来进行我们需要的,或者题目要求的一些限制。
这段话看上去很绕,其实上很妙。
我们在做题的时候会有若干种模型,最小割,最大权闭合子图,或者等等。
我们在做题时的常用套路是建立超级源点(虚拟源点)和超级汇点(虚拟汇点)。
这个时候我说一说我自己的一些想法。
匹配
您配吗?
我们知道二分图和网络流有密不可分的联系但是我现在不是要讲它。
我们在思考网络流题目的时候,我常引用匹配的思想。比如说
我们可以把这个题要干的事情看成一个匹配。
每一个类型要去匹配若干个子项目(题目)。
就是说我们的类型相当于是一个容器,题目要去填充它。
或者说我们要安排一些事情,我们要让一些人在一定内的时间里去修车。那么我们可不可以把这件事看成一个匹配,一个人,一段单位时间,去匹配一辆车。毫无以为,这也是一个一配多的事。在建立了这样的思想基础上,我们可以把建图看成一个可以分步骤的事情,或者说是一个可以具体化的一个过程。我们有了明确的步骤,把脑海中的概念与实际问题中的参量进行对应,从而建模。但是这种东西不是唯一的,这就像二分的模板,每个人要找到适合自己的方法即可。
具体一点,我们把源点连被匹配物,每个被匹配物连匹配物,然后匹配物连汇点。
在这个过程中,容量为inf的边表示强连带关系(我自己瞎BB的名字),就是说他们在答案中必须联通,就是必须在一起。比如说方格取数,你每一个点连四周四个点,就是说你这五个点只能选一个,那么这时候的容量就是个inf,因为这个题是个最小割模型。我们站在最小割上的角度去想,你割掉一些点,就是去取一个最大权闭合子图,但是最小割,就是有的容量为inf的边你一定不能割,所以根据题目要求把强连带关系的两个点建立为Inf。
费用流里面,我们要严格区分流量与费用的概念。以及认清其关系。
流量是限制一种关系的。比如说
我们把能获利的情况的流量设为1表示只有一次,把费用设为-w
把不能获利的情况的流量设为inf表示可以无限次经过这条边,费用设为0
可以通过这个例子体会一下费用流建图
最小割与最大权闭合子图
我们在网络流建模的时候,往往从最小割的角度出发。我们将所有的正权加起来,类似于一个全集的概念。然后我们去找最小割,每一个最小割,表示我们在全集上最小减去的是多少。然后取最小割的过程就相当于是取若干个权闭合子图的意思。
在强连带关系下,边权为inf.获利点与源点想连,损利点与汇点想连。割获利边表示失去这个本来在全集中的收益,割损益边表示花费这么多权。