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;
}