dijkstra 最小费用最大流

前言:众所周知:spfa他死了

滑稽


dijkstra同样为最短路算法,为什么不能跑费用流qwq

好像是因为有负权边的缘故

但是如果我们如果使用某种玄学的将边权都拉回到正数的话

就可以跑了dijkstra,开心qwq


如果我们每条边暴力加上一个很大的值的话,我们还需要记录所经过的边数,还要保证不溢出,十分的毒瘤

考虑给每个节点一个势(ps:不是什么物理学算法,就是为了给他起个名字)

然后将我们的最短路转移\(dis_v=dis_u+w\)改为\(dis_v=dis_u+w+h_u-h_v\)(\(h_i\)是势),保证\(w+h_u-h_v>=0\)

然后我们观察蔡依林(雾他对最短路有什么影响

比如说我们现在有一条\(p_1-p_2-p_3.....p_n\)的这么一条路径

其路径长度则为\((w_1+w_2+w_3...+w_{n-1})+(h_1-h_2)+(h_2-h_3)+(h_3-h_4)+......(h_{n-1}-h_n)\)

然后发现这个玩意\(\to ~ (h_1-h_2)+(h_2-h_3)+(h_3-h_4)+......(h_{n-1}-h_n)=h_1-h_n\)。如此这样,我们在算出加势以后的(无论路线是什么样的)最短路后,在减去\(h_{begin}-h_{end}\)就可以了


接下来的问题就变成了,如何确定一个\(h_i\).

我们先考虑变形一下\(w+h_u-h_v>=0~~\to~~h_u+w>=h_v\)

\(wow\),好像三角形不等式呀(在最短路中对于一条从\(u\)\(v\)的有向边,总有\(dis_u+w>=dix_v\))。

是不是可以考虑将上一次的\(dis\)当做\(h_i\)(每次\(h_i+=dis_i\))呢?

是可以的,为什么?

假设现在有一条\(u\to v\)的有向边

  • 假设他是一条权值是正的(不加势),那么肯定满足\(dis_u+w>=dis_v\)然就是最短路求错了。\(\therefore w+h_u-h_v>=0\)
  • 如果是一条权值是负的话,我们的\(h_i\)是累加的\(dis_i\)的,所以必定存在某一次增广,是的\(v \to u\)的边变到了\(u \to v\)
    然后这次增广(就是将\(v \to u\)的边反向的增广),肯定满足\(dis_v+w==dis_u(w>=0)~~~\to~~dis_v=dis_u-w\)
    然后这时的\(dis_u,dis_v\)已经被我们累加到了\(h_u,h_v\)中,然后我们继续变形\(dis_u-w-dis_v==0 ~~~\to~~ h_u-h_v-w>=0\)
    然后\(-w\)\(u\to v\)这条边的权值。所以这次并不会成为负数

\(\mathcal{So}\)

我们这样的话就能跑dijkstra了。开心\(qwq\)

而且更快,更稳定,也不容易猝死


↓及其丑陋的代码

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
const int maxn=101000;
using std::swap;
using std::min;
struct Edge
{
	int p;
	int w;
	int f;
	int nxt;
};
struct Data
{
	int p;
	int d;
	bool operator <(const Data &a)const
	{
		return d<a.d;
	}
};
int n,m,s,t;
int len;
Data base[maxn<<6];
Data top()
{
	return base[1];
}
void pop()
{
	swap(base[1],base[len--]);
	int pos=1;
	int nxt;
	while(true)
	{
		nxt=pos;
		if(base[pos<<1]<base[nxt]&&(pos<<1)<=len)
			nxt=pos<<1;
		if(base[pos<<1|1]<base[nxt]&&(pos<<1|1)<=len)
			nxt=pos<<1|1;
		if(pos==nxt)	break;
		swap(base[pos],base[nxt]);
		pos=nxt;
	}
}
void push(Data val)
{
	base[++len]=val;
	int pos=len;
	while((pos>>1)&&base[pos]<base[pos>>1])
	{
		swap(base[pos>>1],base[pos]);
		pos>>=1;
	}
	return ;
}
Edge line[maxn<<1];
int head[maxn],tail=-1;
void add(int a,int b,int c,int d)
{
	line[++tail].p=b;
	line[tail].w=c;
	line[tail].f=d;
	line[tail].nxt=head[a];
	head[a]=tail;
}
int h[maxn];
bool vis[maxn];
int dis[maxn];
int from[maxn];
int L[maxn];
int flow[maxn];
int Max_flow,Min_cost;
bool dijkstra(int begin,int end)
{
	len=0;
	for(int i=1;i<=n;i++)
	{
		dis[i]=0x7fffffff;
		flow[i]=0x7fffffff;
		from[i]=L[i]=vis[i]=0;
	}
	dis[begin]=0;
	Data pas;
	pas.p=begin;pas.d=0;
	push(pas);
	while(len)//手写堆怪我喽
	{
		pas=top();pop();
		while(vis[pas.p]&&len>=1)
		{
			pas=top();
			pop();
		}
		if(vis[pas.p]&&!len)	break;
		vis[pas.p]=true;
		dis[pas.p]=pas.d;
		for(int i=head[pas.p];i!=-1;i=line[i].nxt)
			if(line[i].f>0&&!vis[line[i].p]&&dis[line[i].p]>dis[pas.p]+line[i].w+h[pas.p]-h[line[i].p])//判断,带上势
			{
				dis[line[i].p]=dis[pas.p]+line[i].w+h[pas.p]-h[line[i].p];//跟spfa一样的套路,就是多了个势
				flow[line[i].p]=min(line[i].f,flow[pas.p]);
				from[line[i].p]=pas.p;
				L[line[i].p]=i;
				Data nxt;
				nxt.p=line[i].p;nxt.d=dis[line[i].p];
				push(nxt);
			}
	}
	return dis[end]!=0x7fffffff;
}
void MCMA(int begin,int end)
{
	while(dijkstra(begin,end))//差不多跟spfa一样的格式,就是加了个h数组
	{
		int max_flow=flow[end];
		Min_cost+=max_flow*(dis[end]-h[begin]+h[end]);
		Max_flow+=max_flow;
		for(int i=end;i!=begin;i=from[i])
		{
			line[L[i]].f-=max_flow;
			line[L[i]^1].f+=max_flow;
		}
		for(int i=1;i<=n;i++)
			h[i]+=dis[i];//累加,一定要累加,虽然不累加可能过几个点
	}
}
int main()
{
	scanf("%d%d%d%d",&n,&m,&s,&t);
	for(int i=1;i<=n;i++)	head[i]=-1;
	for(int i=1;i<=m;i++)
	{
		int a,b,c,d;
		scanf("%d%d%d%d",&a,&b,&c,&d);
		add(a,b,d,c);add(b,a,-d,0);//建边
	}
	MCMA(s,t);//跑费用流
	printf("%d %d",Max_flow,Min_cost);//输出
	return 0;
}
posted @ 2018-08-05 16:45  Lance1ot  阅读(605)  评论(4编辑  收藏  举报