把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【BZOJ2055】80人环游世界(有源汇有上下界最小费用可行流)

点此看题面

大致题意:\(n\)座城市和\(m\)个人,要求第\(i\)座城市有恰好\(a_i\)个人经过。每个人可以从任意城市出发,但从一座城市只能通向编号更大的城市,且有一定费用。求最少的总费用。

前言

果然是太久没写网络流,这么套路、这么模板的题目,居然搞了半天。。。

有源汇有上下界可行流

相信大家都知道什么是无源汇有上下界可行流。(不知道可以看看这篇博客【LOJ115】无源汇有上下界可行流,然后顺便看看这篇博客【LOJ116】有源汇有上下界最大流,同样对解决此题有帮助)

对于这题,显然我们会套路地想到把一个点拆成两个点,那么就需要从入点向出点连一条上下界皆为\(a_i\)的边。

根据无源汇有上下界网络流的一般套路,我们把这条边拆掉,改为从超级源向出点连一条流量\(a_i\)的边从入点向超级汇连一条流量\(a_i\)的边,然后在入点和出点之间连一条流量\(0\)的边(显然流量\(0\)的边没用,直接不管它)。

然后由于这是有源汇的,我们还需要从源点向每个入点连一条流量\(m\)的边从每个出点向汇点连一条流量\(m\)的边。并且要注意,此时需要从汇点向源点连一条流量\(m\)的边,以保证只有\(m\)个人的限制。

以上的边费用都是\(0\)

至于点与点之间,套路地从出点向入点连边即可,具体实现详见代码。

代码

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100
#define M 10000
#define INF (int)1e9
using namespace std;
int n,m;
class MinCostPossibleFlow//有源汇有上下界最小费用可行流(其实是最小费用最大流模板)
{
	private:
		#define E(x) ((((x)-1)^1)+1)
		int ee,lnk[2*N+10];struct edge {int to,nxt,F,C;}e[2*M+5];
		int lst[2*N+10],IQ[2*N+10],F[2*N+10],C[2*N+10];queue<int> q;
		I bool SPFA()//SPFA求增广路
		{
			RI i,k;for(i=1;i<=2*n+4;++i) F[i]=C[i]=INF;q.push(S),C[S]=0;
			W(!q.empty()) for(i=lnk[k=q.front()],q.pop(),IQ[k]=0;i;i=e[i].nxt)
			{
				if(!e[i].F||C[k]+e[i].C>=C[e[i].to]) continue;
				C[e[i].to]=C[k]+e[lst[e[i].to]=i].C,F[e[i].to]=min(F[k],e[i].F),
				!IQ[e[i].to]&&(q.push(e[i].to),IQ[e[i].to]=1);
			}return F[T]^INF;
		}
	public:
		I void Add(CI x,CI y,CI f,CI c)//连边
		{
			e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].F=f,e[ee].C=c,
			e[++ee].nxt=lnk[y],e[lnk[y]=ee].to=x,e[ee].F=0,e[ee].C=-c;
		}
		int S,T,s,t;I void MCPF()//普通的最小费用最大流
		{
			RI x,res=0;W(SPFA())
			{
				res+=F[T]*C[T],x=T;
				W(x^S) e[lst[x]].F-=F[T],e[E(lst[x])].F+=F[T],x=e[E(lst[x])].to;
			}printf("%d",res);
		}
}F;
int main()
{
	RI i,j,x;scanf("%d%d",&n,&m),F.S=2*n+1,F.T=2*n+2,F.Add(F.t=2*n+4,F.s=2*n+3,m,0);//从汇点向源点连边
	for(i=1;i<=n;++i) scanf("%d",&x),F.Add(F.s,i,m,0),//和超级源汇(S,T)、原图源汇(s,t)连边,不再赘述
		F.Add(i,F.T,x,0),F.Add(F.S,n+i,x,0),F.Add(n+i,F.t,m,0);
	for(i=1;i<=n;++i) for(j=1;j<=n-i;++j) scanf("%d",&x),~x&&(F.Add(n+i,i+j,m,x),0);//点与点之间连边
	return F.MCPF(),0;
}
posted @ 2020-05-21 09:35  TheLostWeak  阅读(159)  评论(0编辑  收藏  举报