zoukankan      html  css  js  c++  java
  • 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))

    然后发现这个玩意( o ~ (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~~ o~~h_u+w>=h_v)

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

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

    是可以的,为什么?

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

    • 假设他是一条权值是正的(不加势),那么肯定满足(dis_u+w>=dis_v)然就是最短路求错了。( herefore w+h_u-h_v>=0)
    • 如果是一条权值是负的话,我们的(h_i)是累加的(dis_i)的,所以必定存在某一次增广,是的(v o u)的边变到了(u o v)
      然后这次增广(就是将(v o u)的边反向的增广),肯定满足(dis_v+w==dis_u(w>=0)~~~ o~~dis_v=dis_u-w)
      然后这时的(dis_u,dis_v)已经被我们累加到了(h_u,h_v)中,然后我们继续变形(dis_u-w-dis_v==0 ~~~ o~~ h_u-h_v-w>=0)
      然后(-w)(u o 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;
    }
    
  • 相关阅读:
    GS 原理及破解 《0day安全》
    Windows 2000 栈溢出 利用异常
    windows2000 堆溢出 利用原理
    数据类中引用virtual
    DevExpress 程序运行后 layoutView 卡片大小发生变化
    DevExpress 标题栏添加搜索功能
    DevExpress 关于alertControl 图片显示
    DevExpress 关于alertControl 改变其大小
    DevExpress GridControl 选择整行被选单元格不变色的设置
    DevExpress 在使用Ribbon皮肤时标题栏不变化的原因
  • 原文地址:https://www.cnblogs.com/Lance1ot/p/9426262.html
Copyright © 2011-2022 走看看