[JZOJ6035]【GDOI2019模拟2019.3.1】大爷 [CodeForces 1061E] Politics【费用流】【线性规划】

本文介绍了一种解决特定线性规划问题的方法,通过构建最大费用流模型,利用差分技巧减少计算复杂度,实现了在两棵树结构上的最优解求解。详细解析了算法流程与实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

在这里插入图片描述
在这里插入图片描述

Solution

观察数据范围,容易想到这是一个简单的线性规划模型。

每一个点可以看做变量xi∈{0,1}x_i\in\{0,1\}xi{0,1}
然后每个限制对应一个等于号的方程,最后要求目标函数最大。

但是直接跑线性规划的单纯形算法会TLE。

我们观察这题的性质,由于保证了整棵树都会有限制,那么如果我们在两棵树上分别差分,即每个点只对应包含它的最小的子树的限制,外面套的就减去。

这样每个点就在两棵树上恰好分别对应一个限制。
这样就容易建图了,对于两个数中的每一个限制都建一个点,一个原来的点看做一条连接两个限制的边,容量为1,费用为w[i]

源点相应向第一棵树限制链容量为限制的点数,费用为0。
第二棵树的限制相应向汇点连边。
跑最大费用最大流即可,如果源点到第一棵树的限制的边以及第二棵树的限制到汇点的边全部满流则合法,否则就是-1

实现上有一点小细节,就是直接将树上的点看做边可能会造成重边比较麻烦,我们可以将每个树点对应的边拆成两条,中间新建一个点,这样就没有重边了。

Code

#include <bits/stdc++.h>
#define fo(i,a,b) for(int i=a;i<=b;++i)
#define fod(i,a,b) for(int i=a;i>=b;--i)
#define N 505
using namespace std;
int n,sx,sy,fs[N],nt[2*N],dt[2*N],fi[N],nx[2*N],dx[2*N],m2,m1,ans;
int pr[N],f[2*N][2*N],fr[N][2],lw[N*2],a1[2*N][2*N],cs[2*N][2*N],dis[2*N],st,ed,d[N*N],ls[2*N],ds[2*N];
bool bz[2*N];
void link(int x,int y)
{
	nt[++m1]=fs[x];
	dt[fs[x]=m1]=y;
}
void lk(int x,int y)
{
	nx[++m2]=fi[x];
	dx[fi[x]=m2]=y;
}
void dfs(int k,int fa)
{
	if(fr[k][0]&&fa!=0) lw[fr[fa][0]]-=lw[fr[k][0]];
	else if(fa!=0) fr[k][0]=fr[fa][0];
	for(int i=fs[k];i;i=nt[i])
	{
		int p=dt[i];
		if(p!=fa) dfs(p,k);
	}
}
void dfs2(int k,int fa)
{
	if(fr[k][1]&&fa!=0) lw[fr[fa][1]]-=lw[fr[k][1]];
	else if(fa!=0) fr[k][1]=fr[fa][1];
	for(int i=fi[k];i;i=nx[i])
	{
		int p=dx[i];
		if(p!=fa) dfs2(p,k);
	}
}
void hb(int x,int y,int l,int c)
{
	cs[x][y]=c;
	cs[y][x]=-c;
	a1[x][++a1[x][0]]=y;
	a1[y][++a1[y][0]]=x;
	f[x][y]=l,f[y][x]=0;
}
bool spfa()
{
	memset(dis,107,sizeof(dis));
	memset(bz,0,sizeof(bz));
	int l=0,r=1;
	d[1]=st,bz[st]=1;
	dis[st]=0;
	while(l<r)
	{
		int k=d[++l];
		fo(i,1,a1[k][0])
		{
			int p=a1[k][i];
			if(f[k][p]&&dis[k]+cs[k][p]<dis[p])
			{
				dis[p]=dis[k]+cs[k][p];
				ls[p]=k;
				if(!bz[p]) bz[p]=1,d[++r]=p;
			}
		}
		bz[k]=0;
	}
	return (dis[ed]<1e9);
}
int main()
{
	cin>>n>>sx>>sy;
	fo(i,1,n) scanf("%d",&pr[i]);
	fo(i,1,n-1)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		link(x,y),link(y,x);
	}
	fo(i,1,n-1)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		lk(x,y),lk(y,x);
	}
	int q,q1;
	cin>>q;
	fo(i,1,q) 
	{
		int x,y;
		scanf("%d%d",&x,&y);
		fr[x][0]=i,lw[i]=y;
	}
	dfs(sx,0);
	cin>>q1;
	fo(i,1,q1) 
	{
		int x,y;
		scanf("%d%d",&x,&y);
		fr[x][1]=i+q,lw[i+q]=y;
	}
	dfs2(sy,0);
	st=q+q1+1,ed=st+1;
	fo(i,1,q) 
	{
		if(lw[i]<0) {printf("-1\n");return 0;}
		hb(st,i,lw[i],0);
	}
	fo(i,1,q1) 
	{
		if(lw[i+q]<0) {printf("-1\n");return 0;}
		hb(i+q,ed,lw[i+q],0);
	}
	fo(i,1,n) 
	{
		hb(fr[i][0],i+q+q1+2,1,-pr[i]);
		hb(i+q+q1+2,fr[i][1],1,0);
	}
	int ans=0;
	while(spfa())
	{
		int k=ed,s=1802201963;
		ds[0]=0;
		while(k!=st) ds[++ds[0]]=k,s=min(s,f[ls[k]][k]),k=ls[k];
		ds[++ds[0]]=st;
		fod(i,ds[0],2)  
		{
			f[ds[i]][ds[i-1]]-=s;
			f[ds[i-1]][ds[i]]+=s;
		}
		ans+=dis[ed]*s;
	}
	fo(i,1,q) if(f[st][i]>0) {printf("-1\n");return 0;}
	fo(i,1,q1) if(f[i+q][ed]>0) {printf("-1\n");return 0;}
	printf("%d\n",-ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值