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
#include
#include
#include
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>1)&&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;
}

转载于:https://www.cnblogs.com/Lance1ot/p/9426262.html

你可能感兴趣的:(dijkstra 最小费用最大流)