6478. 【GDOI2020模拟02.19】C(上下界费用流、费用流消负环)

题目描述


有源汇上下界最小费用最大流

同上下界网络流,把u->v拆成S'->v、u->v和u->T'三条边,其中S'->v和u->v有代价

也可以求出每个点的出入情况D[i],表示(流入-流出)

如果D[i]>0则连S'->i,否则连i->T'

本质是强制必经边满流,然后再平衡流量

然后跑S'->T'求出可行流,再跑原S->T求出最大流

费用流消负环

可以用消圈算法,但是很慢,考虑用上面的"退流"思想

新建源汇点S''和T'',若一条边的代价<0则强制将其满流,计入答案的同时求出每个点的出入情况E[i]

如果E[i]>0则连S''->i,否则连i->T''

然后跑S''->T''的最小费用最大流即可消掉负环

一点思(kou)考(hu)

当一个费用流图无负环时,增广一次后为什么不会出现负环?

这是跑完之后的图,假设中间的是负环

若出现负环(跑完之后出现),则说明增广路径上存在uv两点,使得路径上u->v的距离a和不经过增广路径的距离b满足b-a<0

也就是b<a,那么必然会跑更短的路径,所以矛盾

更复杂的情况同理

code

#include <bits/stdc++.h>
#define fo(a,b,c) for (a=b; a<=c; a++)
#define fd(a,b,c) for (a=b; a>=c; a--)
#define max(a,b) (a>b?a:b)
#define min(a,b) (a<b?a:b)
#define ll long long
#define file
using namespace std;

struct tree{
	int a[701][2],ls[161],s[161],S[161],len,i,j,k,l;
	
	void New(int x,int y) {++len;a[len][0]=y;a[len][1]=ls[x];ls[x]=len;}
	void dfs(int Fa,int t)
	{
		int i;
		
		for (i=ls[t]; i; i=a[i][1])
		if (a[i][0]!=Fa)
		{
			s[a[i][0]]=s[a[i][0]]+s[t];
			dfs(t,a[i][0]);
			S[t]+=S[a[i][0]];
		}
	}
} tr;
int a[3001][4],A[161],B[161],A2[161],B2[161],ls[161],fa[161],S[161],T[161],pa[161],pb[161],
f[161],g[161],G[161],d[1000001],D[161],E[161],n,m,i,j,k,l,len,st,ed,ans,h,t,sum,St,Ed;
bool bz[161];

void New(int x,int y,int f,int w)
{
	++len;
	a[len][0]=y;
	a[len][1]=ls[x];
	ls[x]=len;
	a[len][2]=f;
	a[len][3]=w;
}
void NEW(int x,int y,int f,int w)
{
	if (!f) return;
	
	if (w<0)
	{
		ans+=f*w,E[y]+=f,E[x]-=f;
		New(x,y,0,w);
		New(y,x,f,-w);
		
		return;
	}
	
	New(x,y,f,w);
	New(y,x,0,-w);
}
void add(int x,int y,int A,int B,int w)
{
	if (A>B)
	{
		printf("-1
");
		exit(0);
	}
	
	ans+=A*w;
	D[y]+=A,D[x]-=A;
	NEW(x,y,B-A,w);
}

void work(int st,int ed)
{
	int i,j,k,l;
	
	while (1)
	{
		h=0;t=1;
		d[1]=st;
		memset(f,127,sizeof(f));
		f[st]=0;
		bz[st]=1;
		
		while (h<t)
		{
			for (i=ls[d[++h]]; i; i=a[i][1])
			if (a[i][2] && f[d[h]]+a[i][3]<f[a[i][0]])
			{
				f[a[i][0]]=f[d[h]]+a[i][3];
				g[a[i][0]]=d[h];
				G[a[i][0]]=i;
				
				if (!bz[a[i][0]] && a[i][0]!=ed)
				d[++t]=a[i][0],bz[a[i][0]]=1;
			}
			
			bz[d[h]]=0;
		}
		if (f[ed]>2000000000) break;
		
		sum=2000000000;
		for (i=ed; i!=st; i=g[i])
		sum=min(sum,a[G[i]][2]);
		
		ans+=sum*f[ed];
		for (i=ed; i!=st; i=g[i])
		a[G[i]][2]-=sum,a[G[i]^1][2]+=sum;
	}
}

int main()
{
	freopen("C.in","r",stdin);
	#ifdef file
	freopen("C.out","w",stdout);
	#endif
	
	scanf("%d%d",&n,&m);len=1;st=n+3,ed=n+4;St=n+5,Ed=n+6;
	fo(i,2,n) scanf("%d%d%d%d%d",&fa[i],&A[i],&B[i],&A2[i],&B2[i]),tr.New(fa[i],i),tr.s[i]=B2[i];
	fo(i,1,m)
	{
		scanf("%d%d%d%d",&S[i],&T[i],&pa[i],&pb[i]);
		++tr.S[T[i]];--tr.S[S[i]];
	}
	
	tr.dfs(0,1);
	
//	---
	
	fo(i,2,n)
	add(i,fa[i],max(0,tr.S[i]-B[i]),min(A[i],tr.S[i]),A2[i]);
	fo(i,1,m)
	ans+=pa[i],add(n+1,T[i],0,1,0),add(S[i],n+2,0,1,0),add(T[i],S[i],0,1,pb[i]-pa[i]+(tr.s[T[i]]-tr.s[S[i]]));
	
	k=len;
	add(n+2,n+1,0,2133333333,0);
	fo(i,1,Ed) if (D[i]>0) NEW(st,i,D[i],0); else if (D[i]<0) NEW(i,ed,-D[i],0); //上下界
	l=len;
	fo(i,1,ed) if (E[i]>0) NEW(St,i,E[i],0); else if (E[i]<0) NEW(i,Ed,-E[i],0); //负环
	
//	---
	
	work(St,Ed); //负环
	
	fo(i,1,ed)
	while (ls[i]>l)
	ls[i]=a[ls[i]][1];
	work(st,ed); //上下界
	
	fo(i,1,n+2)
	while (ls[i]>k)
	ls[i]=a[ls[i]][1];
	
	work(n+1,n+2); //最大流
	
//	---
	
	for (i=ls[st]; i; i=a[i][1])
	if (a[i][2])
	{
		printf("-1
");
		return 0;
	}
	printf("%d
",ans);
	
	fclose(stdin);
	fclose(stdout);
	
	return 0;
}

参考资料

https://www.cnblogs.com/leason-lyx/p/11144527.html
https://www.cnblogs.com/Miracevin/p/10028021.html
https://www.itdaan.com/blog/2012/11/20/e12975f2853632e9c471b1b375b5cac7.html

原文地址:https://www.cnblogs.com/gmh77/p/12360139.html