最小费用最大流-SPFA-多路增广

6 篇文章 0 订阅
2 篇文章 0 订阅
背景

在最近的模拟赛当中,我碰到了一题二分图最大权匹配的题,建图比较简单,因为是IOI赛制,所以可以爆OJ,然后呢,打了一发普通的spfa费用流,跑的很慢诶,只拿了70分,都有人AC了呢。那天的讲题,好像说写KM(二分图最大权完美匹配的一种算法)就能过,但是呢,新的东西学还是需要一段时间的,但要订正的话,emm,还是学一学优化的spfa费用流吧,即多路增广一下,事实证明这是能过的。

算法介绍

多路增广非常类似于dinic,然后我不会啊,然后就向高届的大佬请教,大致的算法过程如下
对整一幅图做一遍spfa(这个和一般spfa费用流相似),若汇点的距离为INF(意味着不能增广),那么退出,否则做如下操作
每一次都用dfs进行一次增广,若某一次增广的流量为0,那么spfa一遍重新做
dfs是多路增广与一般增广的最大不同之处,每到一个点u,它要去扩展所有的点v满足dis[u]+vau(边权)==dis[v]
当然,需要使用当前弧优化才能保证复杂度

应用

相对于一般的spfa费用流,多路增广尤其适用于边流量特别小的图(这些图一次spfa如果只扩展一条边那么效率将会变得很低下)

实现代码
#include<cstdio>
#include<cctype>
#include<cmath>
#include<algorithm>
#include<cstring>
namespace fast_IO
{
    const int IN_LEN=10000000,OUT_LEN=10000000;
    char ibuf[IN_LEN],obuf[OUT_LEN],*ih=ibuf+IN_LEN,*oh=obuf;
    char *lastin=ibuf+IN_LEN;
    const char *lastout=ibuf+OUT_LEN-1;
    inline char getchar_()
    {
        if(ih==lastin)lastin=ibuf+fread(ibuf,1,IN_LEN,stdin),ih=ibuf;
        return (*ih++);
    }
    inline void putchar_(const char x)
    {
        if(ih==lastout)fwrite(obuf,1,oh-obuf,stdout),oh=obuf;
        *oh++=x;
    }
    inline void flush(){fwrite(obuf, 1, oh - obuf, stdout);}
}
using namespace fast_IO;
//#define getchar() getchar_()
//#define putchar(x) putchar_((x))
typedef long long LL;
#define rg register
template <typename T> inline T max(const T a,const T b){return a>b?a:b;}
template <typename T> inline T min(const T a,const T b){return a<b?a:b;}
template <typename T> inline T abs(const T a){return a>0?a:-a;}
template <typename T> inline void swap(T&a,T&b){T c=a;a=b;b=c;}
template <typename T> inline T gcd(const T a,const T b){if(a%b==0)return b;return gcd(b,a%b);}
template <typename T> inline T square(const T x){return x*x;};
template <typename T> inline void read(T&x)
{
    char cu=getchar();x=0;bool fla=0;
    while(!isdigit(cu)){if(cu=='-')fla=1;cu=getchar();}
    while(isdigit(cu))x=x*10+cu-'0',cu=getchar();
    if(fla)x=-x; 
}
template <typename T> void printe(const T x)
{
    if(x>=10)printe(x/10);
    putchar(x%10+'0');
}
template <typename T> inline void print(const T x)
{
    if(x<0)putchar('-'),printe(-x);
    else printe(x);
}
const int maxn=5002,maxm=100003;
int N,M,S,T,maxflow,mincost;
int head[maxn],cur[maxn],nxt[maxm],tow[maxm],vau[maxm],cost[maxm],tmp=1;
inline void addb(const int u,const int v,const int w,const int f)
{
    tmp++;
    nxt[tmp]=head[u];
    head[u]=tmp;
    tow[tmp]=v;
    vau[tmp]=w;
    cost[tmp]=f;
}
int dis[maxn],fr[maxn];
int Q[maxn],l,r;
bool Hash[maxn];
inline int spfa()
{
	memset(dis,0x7f,sizeof(dis));
	dis[S]=0;
	Hash[S]=1;
	l=r=0;
	Q[++r]=S;
	while(l!=r)
	{
		l++;if(l==maxn)l=1;
		const int u=Q[l];
		for(rg int i=head[u];i;i=nxt[i])
		{
			const int v=tow[i];
			if(vau[i]&&dis[u]+cost[i]<dis[v])
			{
				dis[v]=dis[u]+cost[i];
				fr[v]=i;
				if(!Hash[v])
				{
					Hash[v]=1;
					r++;if(r==maxn)r=1;
					Q[r]=v;
				}
			}
		}
		Hash[u]=0;
	}
	return dis[T];
}
int dfs(const int u,const int f)
{
	if(f==0||u==T)return f;
	rg int sum=0;
	Hash[u]=1;
	for(rg int i=cur[u];i;i=nxt[i])
	{
		cur[u]=i;
		const int v=tow[i];
		if(dis[v]!=dis[u]+cost[i]||vau[i]==0)continue;
		if(!Hash[v])
		{
			const int ans=dfs(v,min(f-sum,vau[i]));
			sum+=ans,vau[i]-=ans,vau[i^1]+=ans;
			mincost+=cost[i]*ans;
		}
	}
//	cur[u]=head[u];
	Hash[u]=0;
	return sum;
}
inline void flow()
{
	while(spfa()!=0x7f7f7f7f)
	{
		for(rg int i=1;i<=N;i++)cur[i]=head[i];
		while(1)
		{
	      	const int x=dfs(S,0x7f7f7f7f);
		    if(x==0)break;
		    maxflow+=x;
		    if(mincost>=0x7f7f7f3e)return;
    	}
	}
}
int main()
{
    read(N),read(M),read(S),read(T);
    for(rg int i=1;i<=M;i++)
    {
        int u,v,w,f;read(u),read(v),read(w),read(f);
        addb(u,v,w,f),addb(v,u,0,-f);
    }
    flow();
    print(maxflow),putchar(' '),print(mincost);
    return flush(),0;
}
注意事项

使用多路增广网络流使用于一些有性质的图会有很好的效果,但如果只是一般的图的话那么效果就不是那么明显啦(可能是我写的常数比较大吧),所以呢,学多路增广之后,对于普通spfa费用流的熟练掌握还是挺重要的

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值