网络流

前言

由于CSP2021最后一题考了网络流,而本蒟蒻在暑假学了网络流后就没有碰过Dinic所以这几天就开始狂刷网络流的题。

网络流:是一种很奇怪但是又很好玩(玄学)的东西。

她可以用来解决一些多限制的问题,比如说最小费用最大流就在很多方面都有用处。

网络流的板子有点难写(没有一次是一边写对的)。

但是,当把板子背下来后,看着题就把板子贴上去就可以了。

所以,网络流难在建立模型,每一道题的模型都千奇百怪,但又有着内在的联系,这就是我要总结的原因QWQ

1.最大流问题

首先是最大流的板子:(代码丑,多多见谅

#include<bits/stdc++.h>
#define int long long 
#define in read()

const int N=1210;
const int M=120005;
const int INF=0x7f3f3f3f;

int n,m;
int src,des,lev[N],cur[N];
int ecnt,head[N],nxt[M],to[M],cap[M];

inline void addEdge(const int &u,const int &v,const int &w){
	nxt[++ecnt]=head[u],head[u]=ecnt,to[ecnt]=v,cap[ecnt]=w;
	nxt[++ecnt]=head[v],head[v]=ecnt,to[ecnt]=u,cap[ecnt]=0;
}

inline bool Bfs(){
	std::queue<int>q;
	int u,v,e;
	for(int i=1;i<=n;i++){
		lev[i]=-1;
		cur[i]=head[i];
	}
	q.push(src);
	lev[src]=0;
	while(!q.empty()){   
		u=q.front();
		q.pop();
		for(e=head[u];e;e=nxt[e]){
			if(cap[e]>0&&lev[v=to[e]]==-1){
				lev[v]=lev[u]+1;
				q.push(v);
				if(v==des) return 1;
			}
		}
	}
	return 0;
}

inline int Dinic(int u,int flow){
	if(u==des) return flow;
	int res=0,v,delta;
	for(int &e=cur[u];e;e=nxt[e]){
		if(cap[e]>0&&lev[u]<lev[v=to[e]]){
			delta=Dinic(v,std::min(cap[e],flow-res));
			if(delta){
				cap[e]-=delta;
				cap[e^1]+=delta;
				res+=delta;
				if(res==flow) break;
			}
		}
	}
	if(res!=flow) lev[u]=-1;
	return res;
}

inline int maxFlow(){
	int ans=0;
	while(Bfs()) ans+=Dinic(src,INF);
	return ans;
}
inline int read(){
	static char ch;
	int res=0,sign=1;
	while((ch=getchar())<'0'||ch>'9'){
		if(ch=='-'){
			sign=-1;
		}
	}
	res=ch-'0';
	while((ch=getchar())>='0'&&ch<='9'){
		res=res*10+ch-'0';
	}
	return res*sign;
}
inline void print(int x){
	if(x<0){
		x=~x+1;
		putchar('-');
	}
	if(x>9) print(x/10);
	putchar(x%10+'0');
}
signed main()
{
	n=in,m=in,src=in,des=in;
	ecnt=1;
	for(int i=1;i<=m;i++){
		int u,v,w;
		u=in,v=in,w=in;
		addEdge(u,v,w);
	}
	print(maxFlow());
	return 0;
}

最大流在二分图匹配上也有用处只不过跑的比匈牙利慢一点O(n2m)

放一道

飞行员配对问题

标准的二分图匹配:

用最大流,先建立一个源点在建立一个汇点,在源点与左部点之间建一条流量为一的边,在汇点和右部点之间建边。再在中间建边,跑最大流。

#include<bits/stdc++.h>
#define in read()
using namespace std;
const int N=5e4;
int n,m,k;
int src,des;
int head[N],to[N*2],nxt[N*2],cap[N*2];
int cur[N],lev[N];
queue<int> q;
int cnt=1;
void add(int u,int v,int z){
	nxt[++cnt]=head[u],head[u]=cnt,to[cnt]=v,cap[cnt]=1;
	nxt[++cnt]=head[v],head[v]=cnt,to[cnt]=u,cap[cnt]=0;
}
inline bool bfs(){
	for(int i=0;i<=n+m+1;i++){
		cur[i]=head[i];
		lev[i]=-1;
	}
	q.push(src);
	lev[src]=0;
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(int i=head[u];i;i=nxt[i]){
			int v=to[i];
			if(cap[i]>0&&lev[v]==-1){
				lev[v]=lev[u]+1;
				q.push(v);
			}
		}
	}
	if(lev[des]!=-1){
		return 1;
	}else{
		return 0;
	}
}

inline int dfs(int u,int flow){
	if(u==des){
		return flow;
	}
	int res=0,delta;
	for(int i=cur[u];i;i=nxt[i]){
		cur[u]=1;
		int v=to[i];
		if(cap[i]>0&&lev[v]==lev[u]+1){
			delta=dfs(v,min(flow-res,cap[i]));
			if(delta){
				cap[i]-=delta;
				cap[i^1]+=delta;
				res+=delta;
				if(res==flow){
					break;
				}
			}
		}
	}
	return res;
}

inline int dinic(){
	int ans=0;
	while(bfs()){
		ans+=dfs(src,0x3f3f3f3f);
	}
	return ans;
}
int cut[1001][1001];
inline int read(){
	static char ch;
	int res=0,sign=1;
	while((ch=getchar())<'0'||ch>'9'){
		if(ch=='-'){
			sign=-1;
		}
	}
	res=ch-'0';
	while((ch=getchar())>='0'&&ch<='9'){
		res=res*10+ch-'0';
	}
	return res*sign;
}
inline void print(int x){
	if(x<0){
		x=~x+1;
		putchar('-');
	}
	if(x>9) print(x/10);
	putchar(x%10+'0');
}
int main()
{
	n=in,m=in,k=in;
	src=0;
	des=n+m+1;
	for(int i=1;i<=n;i++){
		add(src,i,1);
	}
	for(int i=n+1;i<=n+m;i++){
		add(i,des,1);
	}
	for(int i=1;i<=k;i++){
		int x,y;
		x=in,y=in;
		if(!cut[x][y+n]){
			add(x,y+n,1);
			cut[x][y+n]=1;
		}
	}
	print(dinic());
	
}

2.最小割

根据一堆大佬的证明,我们可以清晰的知道最大流=最小割(别问我,我也不知道)。

这样就可以做水题了。

—————分割线——————

是这样的,在打完上面的话后,我去找了一道最小割的题来做。是道蓝题,我做了两个小时,看了题解才过。

就是它:最小割点

一开始看了,打了个Dinic模板结果只有80分,在看题返现是割点不是割边。

看了大佬的题解才懂。

要一个节点建立两个点,一个是入点,一个是出点。两个内部点之间连一条1的边,其他边也都是1.
再跑Dinic;

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5;
const int inf=0x3f3f3f3f;
int n,m,src,des;
int head[N],to[N*4],nxt[N*4],cap[N*4];
int cnt=1;
void add(int u,int v,int p){
	nxt[++cnt]=head[u],head[u]=cnt,to[cnt]=v,cap[cnt]=p;
	nxt[++cnt]=head[v],head[v]=cnt,to[cnt]=u,cap[cnt]=0;
}
int dis[N],cur[N];
queue<int> q;
inline bool bfs(){
	for(int i=1;i<=4*n;i++){
		cur[i]=head[i];
		dis[i]=-1;
	}
	q.push(src);
	dis[src]=0;
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(int i=head[u];i;i=nxt[i]){
			int v=to[i];
			if(cap[i]>0&&dis[v]==-1){
				dis[v]=dis[u]+1;
				q.push(v);
			}
		}
	}
	return dis[des]!=-1;
}
int ff[N],in[N];
inline int dfs(int u,int flow){
	in[N]+=flow;
	if(u==des){
		return flow;
	}
	int res=0,delta;
	for(int i=cur[u];i;i=nxt[i]){
		cur[u]=1;
		int v=to[i];
		if(cap[i]&&dis[v]==dis[u]+1){
			delta=dfs(v,min(flow-res,cap[i]));
			if(delta){
				cap[i]-=delta;
				cap[i^1]+=delta;
				res+=delta;
				ff[u]+=delta;
				if(res==flow){
					return 1;
				}
			}
		}
	}
	if(res!=flow){
		dis[u]=-1;
	}
	return res;
}
inline int read(){
	static char ch;
	int res=0,sign=1;
	while((ch=getchar())<'0'||ch>'9'){
		if(ch=='-'){
			sign=-1;
		}
	}
	res=ch-'0';
	while((ch=getchar())>='0'&&ch<='9'){
		res=res*10+ch-'0';
	}
	return res*sign;
}
inline void print(int x){
	if(x<0){
		x=~x+1;
		putchar('-');
	}
	if(x>9) print(x/10);
	putchar(x%10+'0');
}
inline int dinic(){
	int ans=0;
	while(bfs()){
		ans+=dfs(src,inf);
	}
	return ans;
}
signed main()
{
	n=read(),m=read();
	src=read(),des=read();
	int x,y;
	for(int i=1;i<=n;i++){
		if(i==src){
			add(i,i+n,inf);
		}else{
			add(i,i+n,1);
		}
	}
	for(int i=1;i<=m;i++){
		x=read(),y=read();
		if(x==src&&y==des||x==des&&y==src){
			cout<<1<<endl;
		}
		add(x+n,y,1);
		add(y+n,x,1);
	}
	cout<<dinic()<<endl;
	return 0;
}

然后才过了……

3.费用流

在最大流的基础上,加上费用限制。

一道好玩的题:餐巾计划
传送门

建模很妙:

#include<bits/stdc++.h>
#define N 2*n+1
#define inf 2147483647
#define in read()
using namespace std;
#define int long long
struct node{
	int u,nxt,w,f;
}e[300000];
long long n,p,b,f,a,s,st=1,cost,ans;
int day[100000],dis[100000],fir[100000],cur[100000];
queue<int>q;
bool v[100000];

bool spfa()
{
	for(int i=0;i<=N;i++){
		dis[i]=inf/2;
		cur[i]=fir[i];
	}
	q.push(0);
	dis[0]=0;
	while(!q.empty()){
		int k=q.front();
		q.pop();
		for(int i=fir[k];i;i=e[i].nxt){
			int u=e[i].u;
			int w=e[i].f;
			if(dis[u]>dis[k]+w&&e[i].w){
				dis[u]=dis[k]+w;
				if(!v[u]) q.push(u);
			}
		}
	}
	return (dis[N]<inf/2);
}

int dfs(int p,int flow){
	if(p==N){
		v[N]=1;
		ans+=flow;
		return flow;
	}
	int delta=0,used=0;
	for(int i=cur[p];i;i=e[i].nxt){
		cur[p]=1;
		int u=e[i].u,w=e[i].f;
		if(dis[u]==dis[p]+w&&e[i].w){
			if(delta=dfs(u,min(flow-used,e[i].w))){
				e[i].w-=delta;
				e[i^1].w+=delta;
				used+=delta;
				cost+=w*delta;
				if(used==flow){
					break;
				}
			}
		}
	}
	return used;
}

long long dinic()
{
	while(spfa()){
	
	dfs(0,inf);
		
	}
	return cost;
}

void add(int x,int y,int w,int f){
	e[++st].u=y,e[st].nxt=fir[x];e[fir[x]=st].w=w,e[st].f=f;
	e[++st].u=x,e[st].nxt=fir[y],e[fir[y]=st].w=0,e[st].f=-f;
}
inline long long read(){
	static char ch;
	long long  res=0,sign=1;
	while((ch=getchar())<'0'||ch>'9'){
		if(ch=='-'){
			sign=-1;
		}
	}
	res=ch-'0';
	while((ch=getchar())>='0'&&ch<='9'){
		res=res*10+ch-'0';
	}
	return res*sign;
}
inline void print(int x){
	if(x<0){
		x=~x+1;
		putchar('-');
	}
	if(x>9) print(x/10);
	putchar(x%10+'0');
}
signed main()
{
	n=in;
	for(int i=1;i<=n;i++){
		day[i]=in;
		add(0,i,day[i],0);
		add(i+n,N,day[i],0);
	}
	p=in,a=in,f=in,b=in,s=in;
	for(int i=1;i<=n;i++)
	{
		add(0,i+n,inf,p);
		if(i+1<=n) add(i,i+1,inf,0);
		if(i+a<=n) add(i,i+n+a,inf,f);
		if(i+b<=n) add(i,i+n+b,inf,s);
	}
	print(dinic());
	
	return 0;
}

是不是很妙呀!!QWQ

又一道水题:传送门

把多的点和少的点分开和源点还有汇点建边。
然后再建环边。

#include<bits/stdc++.h>
#define inf 2147483647
using namespace std;
int n;
struct node{
	int flow,price,nxt,to;
}e[300001];
int head[10001],dis[10001],cur[10001],cnt=1;
int cost;
int src,des;
void add(int x,int y,int f,int c){
	e[++cnt].nxt=head[x],head[x]=cnt,e[cnt].to=y,e[cnt].price=c,e[cnt].flow=f;
	e[++cnt].nxt=head[y],head[y]=cnt,e[cnt].to=x,e[cnt].price=-c,e[cnt].flow=0;
}
queue<int> q;
inline bool spfa()
{
	
	for(int i=0;i<=n+3;i++){
		dis[i]=inf/2;
		cur[i]=head[i];
	}
	q.push(src);
	dis[src]=0;
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].to;
			if(dis[v]>dis[u]+e[i].price&&e[i].flow){
				dis[v]=dis[u]+e[i].price;
				q.push(v);
			}
		}
	}
	return (dis[des]<inf/2);
}

//inline int dfs(int now,int flow){
//	if(now==des){
//		cost+=dis[des]*flow;
//		return flow;
//	}
//	int res=0,delta;
//	for(int i=cur[now];i;i=e[i].nxt){
//		cur[now]=1;
//		int v=e[i].to;
//		if(e[i].flow&&dis[v]==dis[now]+e[i].price){
//			delta=dfs(v,min(flow-res,e[i].flow));
//			if(delta){
//				e[i].flow-=delta;
//				e[i^1].flow+=delta;
//				res+=delta;
//				if(res==flow){
//					break;
//				}
//			}
//		}
//	}
//	return res;
//}

inline int dfs(int now,int flow){
	if(now==des){
		cost+=dis[des]*flow;
		return flow;
	}
	int res=0,delta=0;
	for(int i=cur[now];i;i=e[i].nxt){
		cur[now]=1;
		int v=e[i].to;
		if(dis[v]==dis[now]+e[i].price&&e[i].flow){
			delta=dfs(v,min(flow-res,e[i].flow));
			if(delta){
				e[i].flow-=delta;
				e[i^1].flow+=delta;
				res+=delta;
				if(res==flow){
					break;
				}
			}
		}
	}
	return res;
}
inline int dinic()
{
	while(spfa()){
		dfs(src,inf);
	}
	return cost;
}
int a[10001],sum;
inline int read(){
	static char ch;
	int  res=0,sign=1;
	while((ch=getchar())<'0'||ch>'9'){
		if(ch=='-'){
			sign=-1;
		}
	}
	res=ch-'0';
	while((ch=getchar())>='0'&&ch<='9'){
		res=res*10+ch-'0';
	}
	return res*sign;
}
inline void print(int x){
	if(x<0){
		x=~x+1;
		putchar('-');
	}
	if(x>9) print(x/10);
	putchar(x%10+'0');
}
int main(){
    n=read();
    for(int i=1;i<=n;++i){
        a[i]=read();
        sum+=a[i];
    }
    sum/=n;src=0;des=n+1;
    for(int i=1;i<=n;++i)	a[i]-=sum;
    for(int i=1;i<=n;++i){
        if(a[i]>0)
            add(src,i,a[i],0);
        else if(a[i]<0)
            add(i,des,-a[i],0);
    }
    for(int i=1;i<=n;++i){
        if(i!=1)
            add(i,(i-1),inf,1);
        if(i!=n)
            add(i,(i+1),inf,1);
    }
    add(1,n,inf,1);
    add(n,1,inf,1);
    dinic();
    print(cost);
    return 0;
}

还是一道水题:

更水了。

传送门

感觉越来越水了~~~~

posted @   SSZX_loser_lcy  阅读(110)  评论(0编辑  收藏  举报
编辑推荐:
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示