[模板] 最小费用最大流

一、题目

点此看题

二、解法

讲一种势能 \(\tt dijkstra\) 的做法(简称势能算法),因为 \(\tt spfa\) 在单次扩展的时候可能会被卡到 \(O(nm)\),而势能 \(\tt dijkstra\) 的时间复杂度是严格的 \(O(m\log n)\),在一些扩展次数较小的毒瘤题中可能会用到。

势能算法的中心思路是化负权边为非负权边,我们设计势能函数 \(h(x)\) 来调整边权,这里的势能和物理中的定义是很相似的,也就是势能变化之和初末位置有关,而和路径无关。

\(w'(u,v)=w(u,v)+h(u)-h(v)\),不难发现最后跑出来的最短路要加上 \(h(t)-h(s)\)

关键是如何使得 \(w(u,v)+h(u)-h(v)\geq0\) 恒成立,设 \(d(u)\)\(s\)\(u\) 真正的最短路,设 \(dis(u)\)\(s\)\(u\) 通过转化后的边求出来的最短路,令 \(h(s)=0\),有 \(dis(u)+h(u)=d(u)\)

如果我们再每次做完最短路之后让 \(h(u)\leftarrow d(u)\) 是满足条件的,因为如果是原来的边那么显然 \(d(u)-d(v)+w(u,v)\geq 0\),如果是新产生的取负的边那么 \(d(u)=d(v)+w,(w>0)\rightarrow d(u)-d(v)-w=0\),新的边权就是 \(0\),也满足条件。那么每次我们把 \(h(u)\) 加上 \(dis(u)\) 就可以维护势能函数了。

时间复杂度 \(O(flow\cdot m\log n)\)

#include <cstdio>
#include <queue>
using namespace std;
const int M = 50005;
const int inf = 0x3f3f3f3f;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,s,t,tot,f[M],dis[M],fw[M],pre[M],lst[M],h[M];
struct edge
{
	int v,f,c,next;
}e[2*M];
struct node
{
	int u,c;
	bool operator < (const node &b) const
	{
		return c>b.c;
	}
};
void add(int u,int v,int F,int c)
{
	e[++tot]=edge{v,F,c,f[u]},f[u]=tot;
	e[++tot]=edge{u,0,-c,f[v]},f[v]=tot;
}
int bfs()
{
	for(int i=1;i<=n;i++) dis[i]=inf;
	fw[s]=inf;pre[s]=lst[s]=dis[s]=0;
	priority_queue<node> q;
	q.push(node{s,0});
	while(!q.empty())
	{
		node t=q.top();q.pop();int u=t.u;
		for(int i=f[u];i;i=e[i].next)
		{
			int v=e[i].v,c=h[u]-h[v]+e[i].c;
			if(dis[v]>dis[u]+c && e[i].f)
			{
				dis[v]=dis[u]+c;
				pre[v]=u;lst[v]=i;
				fw[v]=min(fw[u],e[i].f);
				q.push(node{v,dis[v]});
			}
		}
	}
	return dis[t]<inf;
}
signed main()
{
	n=read();m=read();s=read();t=read();tot=1;
	for(int i=1;i<=m;i++)
	{
		int u=read(),v=read(),f=read(),c=read();
		add(u,v,f,c);
	}
	int ans=0,flow=0;
	while(bfs())
	{
		int zy=t;
		flow+=fw[t];
		ans+=(dis[t]+h[t])*fw[t];
		while(zy!=s)
		{
			e[lst[zy]].f-=fw[t];
			e[lst[zy]^1].f+=fw[t];
			zy=pre[zy];
		}
		for(int i=1;i<=n;i++)
			h[i]+=dis[i];
	}
	printf("%d %d\n",flow,ans);
}
posted @ 2021-08-15 21:23  C202044zxy  阅读(48)  评论(0编辑  收藏  举报