网络流

文中流和费用是不一样的两个代价

关于反向边,理解很玄,每次理解的又不一样,想到因为答案是全局统计的,所以如果只是单向建边当选到不是最优路径时将无法退回。所以建反向边,初始容量为 0,正向边每 -1,反向边就 +1,想一条 U——V——A 的路径(u,v,a之间还有很多点),如果选到 V 都是最优路径,那选到 A 时发现 V——A 不是最优的,那从 A——V 退回来走反向边到达 V 以后,全局所统计的量与开始从 U——V 时的量是一样的,所以反向边是用来反悔的。

P3376 【模板】网络最大流
E.K:简单来讲就是不断的从源点到汇点找路径,称作增广路,找到一条以后,那该条路径上的每条边的容量都要减去路径上的最小容量,对最大流的贡献就是该条路径上的最小容量。而答案在找不出增广路时结束计算,整个过程感觉就是不断地在从源点往汇点送流。感觉学 E.K 纯粹是为了最小费用最大流(其实 Dinic 也可以求最小费用最大流)。

具体

Edmonds-Karp 提交

//bfs 找增广路:
int bfs(){
	memset(vis,0,sizeof(vis));
	while(!q.empty())	q.pop();
	q.push(s);ll[s]=INF;
	vis[s]=1;//遍没遍历过
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(int i=head[u];i!=-1;i=e[i].next){
			if(e[i].w ==0)	continue;
			int v=e[i].to;
			if(vis[v])	continue;
			vis[v]=1;
			ll[v]=min(ll[u],e[i].w);//路径上的最小容量
			before[v]=i;//前驱方便回溯
			q.push(v);
			if(v==t)	return 1;
		}
	}
	return 0;
}

//改正反边:
void hugai(){
	int u=t;
	while(u!=s){
		int i=before[u];
		e[i].w-=ll[t]; 
		e[i^1].w+=ll[t];
		u=e[i^1].to;//把当前的增广路给抽出来
	}
	ans+=ll[t];//对答案的贡献肯定就是路上最小可送的流啊(最小容量)
}

由此推出 最小费用最大流 的算法,就是在选每条增广路时保证从源点到汇点的费用都是最小的,从源点每送一点流到汇点都保证费用最小。代码几乎就没啥变动,用 spfa 套上就行了。
P3381 【模板】最小费用最大流

变动

提交

int spfa(){
//	int hh=0,tt=1;
	memset(d,0x3f,sizeof(d));//d是走到当前点的费用(当前点流出的费用)
	memset(ll,0,sizeof(ll));
	memset(vis,0,sizeof(vis));
	while(!q.empty())	q.pop();
	q.push(s);d[s]=0;ll[s]=INF;
	vis[s]=1;//在不在队
	while(!q.empty()){
		int u=q.front();
		q.pop();
		vis[u]=0;
		for(int i=head[u];i!=-1;i=e[i].next){
			int v=e[i].to;
			if(e[i].w&&d[v]>d[u]+e[i].c){
				d[v]=d[u]+e[i].c;
				before[v]=i;
				ll[v]=min(ll[u],e[i].w);
				if(!vis[v]){
					q.push(v);
					vis[v]=1;		
				} 
			}
		}
	} 
	return ll[t]>0;//如果汇点流量大于1,说明遍历过汇点(即有增广路) 
  /*这里为什么不能直接发现 v==t 就 return 1,因为有可能发现的路径不是最优的 */
}

这样一条一条,一点一点的找流,复杂度不优。
Dinic:想到因为 E.K 作为 bfs,所以一次只能找一条增广路,那我能否对于一个点衍生出去所有边找增广路呢?首先想到可以回溯实现,则要 dfs,对于一个点,dfs 的去找所有的边,在回溯的时候再把当前边所做的贡献加上。

至于具体的优化,有当前弧,删点,分层图...反正就很启发式。

“Dinic 本身就是一个启发式”——石队

其他的优化并不难理解,主要是分层,石队把他归类为启发式优化,对于一个点,如果从源点到达他的路径越长,那路径上最小容量就越有可能更小,所以对于一个点,尽量让从源点到他的路劲按 bfs 分层那样变短些。(说实话我也觉得这有点感性了)

具体

Dinic 提交
才发现我 Dinic 复杂度是假的...

int bfs(){//计算当前图的层次d[] 
	for(int i=1;i<=n;i++)	d[i]=0;
	while(!q.empty())	q.pop();
	q.push(s);
	d[s]=1;
	cur[s]=head[s];
	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&&!d[v]){
				q.push(v);
				d[v]=d[u]+1;
				cur[v]=head[v];	
				if(v==t)	return 1;
			}
		}
	}
	return 0; 
}

int dfs(int u,int rl){//rl表示当前点剩余的容量 
	if(u==t)	return rl;
	int sum=0;
	for(int i=cur[u];i!=-1&&rl;i=e[i].next){
		cur[u]=i;//当前弧优化
		int v=e[i].to;
		if((d[v]==d[u]+1)&&e[i].w){
			int f=dfs(v,min(rl,e[i].w));
			if(f==0)	d[v]=0;
			e[i].w-=f;
			e[i^1].w+=f;
			sum+=f;
			rl-=f;
			if(rl==0)	break ;
		} 
	}
	if(sum==0)	d[u]=0;//删点
	return sum; 
}

最小割:对于一个网,定义割一条边的代价为他的容量,问最少要多少代价,使得源点和汇点不再互达。

一个网的最小割等于它的最大流,简单证明一下:要想源点汇点不连通,就是让图中没有增广路,所以对于每一条增广路都要把它割断,至少在路上割一条边,那肯定割最小代价的边,也就是容量最小的边,对于每一条增广路割它容量最小的边,这不就等价于求最大流中对于每一条增广路取它容量最小的边做贡献吗?所以感性得证。

原来无向图的最大流,就是将原反向边的边权 0,改为和正向边一样的边权。

posted @ 2025-12-26 14:45  Lywh  阅读(7)  评论(0)    收藏  举报