网络流

网络是一个有向图\(G=(V,E)\),每一条边有一个流量限制\(w\),流过这条边的流量不能超过\(w\),可以理解为一个水管组成的图,每个水管的流量都是固定的,结点不能存水,只能决定从每条入边流进来多少水,每个出边流出多少水,并且流入的水等于流出的水;

网络流问题分为有源汇与无源汇两种,最大流即一种有源汇问题。

最大流

在一张有源汇网络中,求起点\(s\)到终点\(t\)的最大流量

增广

常见的最大流解法之一,另一种有机会再学。

约定

定义一条增广路为从\(s\)\(t\)的一条路经,使得当前流到终点\(t\)的流量增加。
定义参量网络\(G*\)\(G\)增广后所有剩余容量大于0的边与有效节点组成的网络
对每一条边\((u,v)\)建一条反边\((v,u)\),初始流量限制为0,当一条边流量\(-k\)时,让他的反边\(+k\)

为什么这样是对的呢?假设网络已经被一条增广路一次,重新从源点搜到第一条增广路上的一个结点\(u\)时,走其中一条反边\((u,v)\)就相当于撤回上一次增广中\((v,u)\)流过的部分流量换成这次流进\(u\)的流量,然后继续从\(v\)出发继续流,从而简单的支持反悔。

建反边有一种简洁的方式,通过链式反向星存图,令边的编号为\(i\)的边的反边编号为\(i\)^1,这样2,3是一对反边,4,5是一对反边……但是初始的cnt=0要换成cnt=1。

Edmonds-Karp

思想:不断通过从源点\(s\) bfs寻找长度最短的增广路,时间复杂度\(O(n*m^2)\)

每次在增广路上的边\((E_1)\)的剩余流量减去增广的流量\(k\),增广路上的反边\((E_1')\)加k。

当源点\(s\)与汇点\(t\)不连通时说明残量网络不存在增广路了,结束循环返回结果。

模板题P3376code:

#include<iostream>
#include<cstring>
#include<queue>
#include<algorithm>
#include<unordered_map>
#include<vector>
#define gc getchar
#define N 205
#define M 5005
#define inf (int)(1e18)
#define pii pair<int,int>
#define nzn puts("***************")
#define r1(x,a,b) for(int x=a;x<=b;x++)
#define int long long
int rd(){
	int x=0,f=1;char c=gc();
	for(;!isdigit(c);c=gc())f=c==45?-1:f;
	for(; isdigit(c);c=gc())x=x*10+c-'0';
	return x*f;
}
using namespace std;
int n,m,s,t;
struct G_Flows{
	int hd[N],cnt=1,fl[N],prv[N];
	struct G_Edge{int v,nxt,w;}e[M<<1];
	void add(int u,int v,int w){
		e[++cnt]={v,hd[u],w},hd[u]=cnt;
		e[++cnt]={u,hd[v],0},hd[v]=cnt;
	}
	int upd(int s,int t){
		int ret=0;
		while(1){
			r1(i,1,n)fl[i]=-1;
			fl[s]=inf;
			queue <int> q;
			q.push(s);
			while(!q.empty()){
				int u=q.front();q.pop();
				for(int i=hd[u];i;i=e[i].nxt){
					int v=e[i].v;
					if(fl[v]==-1&&e[i].w)q.push(v),fl[v]=min(fl[u],e[i].w),prv[v]=i;
				}
			}
			if(fl[t]==-1)return ret;
			ret+=fl[t];
			for(int i=prv[t];i;i=prv[ e[i^1].v ] )e[i].w-=fl[t],e[ i^1 ].w+=fl[t];
		}
	}
}g;
signed main(){
	n=rd(),m=rd(),s=rd(),t=rd();
	r1(i,1,m){
		int u=rd(),v=rd(),w=rd();
		g.add(u,v,w);
	}
	printf("%lld",g.upd(s,t));
	return 0;
}

Dinic

dinic算法与EK思想差不多,都是通过不断增广求最大流,但时间复杂度为\(O(n^2m)\),实际甚至远远跑不到,在稠密图上优势更加显著。dinic的优势在于可以进行多路同时增广,算法流程:对残量网络bfs分层,再dfs多路增广,传入当前结点和流量,再流入下一个结点增广

对网络分层的意义是可以把剩下的网络看作一个dag防止dfs一直在环上跑。

dinic的一个关键优化:当前弧优化

流满的边没有dfs的必要,我们可以对每个节点\(u\)记录第一个没有流满的边\(cur[u]\),直接从这个边开始流,\(cur[u]\)初始值为\(hd[u]\)

模板题P3376code:

#include<iostream>
#include<cstring>
#include<queue>
#include<algorithm>
#include<unordered_map>
#include<vector>
#define gc getchar
#define N 205
#define M 5005
#define inf (int)(1e18)
#define pii pair<int,int>
#define nzn puts("***************")
#define r1(x,a,b) for(int x=a;x<=b;x++)
#define int long long
int rd(){
	int x=0,f=1;char c=gc();
	for(;!isdigit(c);c=gc())f=c==45?-1:f;
	for(; isdigit(c);c=gc())x=x*10+c-'0';
	return x*f;
}
using namespace std;
int n,m,s,t;
struct G_Flows{
	int hd[N],cnt=1,cur[N],dep[N];
	struct G_Edge{int v,nxt,w;}e[M<<1];
	void add(int u,int v,int w){
		e[++cnt]={v,hd[u],w},hd[u]=cnt;
		e[++cnt]={u,hd[v],0},hd[v]=cnt;
	}
	int dfs(int u,int res){
		if(u==t)return res;
		int ret=0;
		for(int i=cur[u];i&&res;i=e[i].nxt){
			cur[u]=i;
			int v=e[i].v;
			if(dep[v]==dep[u]+1&&e[i].w){
				int k=dfs(v,min(e[i].w,res));
				res-=k,e[i].w-=k,e[i^1].w+=k,ret+=k;
			}
		}
		if(ret==0)dep[u]=-1;
		return ret;
	}
	int upd(int s,int t){
		int ret=0;
		int ed=t;
		while(1){
			r1(i,1,n)cur[i]=hd[i],dep[i]=-1;
			dep[s]=0;
			queue<int>q;q.push(s);
			while(!q.empty()){
				int u=q.front();q.pop();
				for(int i=cur[u];i;i=e[i].nxt){
					int v=e[i].v;
					if(dep[v]==-1&&e[i].w)q.push(v),dep[v]=dep[u]+1;
				}
			}
			if(dep[t]==-1)return ret;
			ret+=dfs(s,inf);
		}
	}
}g;
signed main(){
	n=rd(),m=rd(),s=rd(),t=rd();
	r1(i,1,m){
		int u=rd(),v=rd(),w=rd();
		g.add(u,v,w);
	}
	printf("%lld",g.upd(s,t));
	return 0;
}

做题记录:

I.[P3254 圆桌问题] ( https://www.luogu.com.cn/problem/P3254

将一个代表看作一点流量,从源点\(S\)连m条边到结点\([1,m]\)代表\(m\)个单位,边权为\(r_i\);从\([m+1,m+n]\)分别连\(n\)条边到\(T\),再从每个单位\([1,m]\)连一条流量限制为1的边到每个桌\([m+1,m+1]\),跑一边最大流,若最大流\(\ne tot(人数)\)输出零,否则根据网络信息构造方案即可。主函数部分代码:

signed main(){
	int mb=(&b)-(&a);
	m=rd(),n=rd(),t=m+n+1;
	r1(i,1,m){
		int w=rd();tot+=w,
		g.add(0,i,w);
	}
	r1(i,1,n){
		int w=rd();
		g.add(i+m,t,w);
	}
	r1(i,1,m)
	    r1(j,1,n)g.add(i,m+j,1);
	if(g.Maxflow(0,t)!=tot)return puts("0"),0;
	puts("1");
	r1(i,1,m){
		for(int j=g.hd[i];j;j=g.e[j].nxt){
			if(g.e[j].w==0&&g.e[j].v>m)cout<<g.e[j].v-m<<" ";
		}
		cout<<"\n";
	}
	return 0;
}

II.P3358 最长k可重区间集问题

有点难想,看到每个点都只能最多有 \(k\) 个区间覆盖,那么就要想办法构造流量限制为 \(k\) 的一些边来满足这个条件。

因为每个点最多有 \(k\) 个区间覆盖不好表示,所以转换为每个点可以有 \(k\) 个流量,然后选了一个线就在

我们把每个点 \(i\) 连一条容量无穷大的边到 \(i+1\),然后把源点 \(S\)\(1\) 连一条容量为 \(k\) 的边,

posted @ 2026-05-31 08:27  Sayhere  阅读(2)  评论(0)    收藏  举报