【模拟赛】树

贪心。考虑自底向上统计答案。

我们先令答案为 \(\sum b_i\)

如果处理到以 \(x\) 为根的子树,那么如果有 \(b\) 比他大的尽量连,就合并了答案。若还有剩下的比他 \(b\) 小的可以连,也连。

如果不能处理完儿子节点的 \(b\),那么只好让儿子不合并,仍贡献答案。

注意,为了保证连儿子时儿子的度数一定不超限,必须留一条边留给父亲。

那么问题来了,当我本来就处理不完儿子,多给一条就多处理一点时,是把这条边给儿子还是给父亲呢?

应该给儿子。因为当给儿子时,这一个点对合并答案做出了最大极限的贡献,一个点最大的合并答案为 \(b_x\times p_x\)。而这是做到了,所以连儿子答案一定不会变劣。

这也算是一个很好很通用的结论了吧。。

code

注意一下关于边界的特判。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5;
int T,n,lim[N+5],du[N+5];ll ans,b[N+5];
int sta[N+5],top,v[N+5];
int head[N+5],to[2*N+5],nxt[2*N+5],ecnt=1;
void add(int u,int v) {to[++ecnt]=v;nxt[ecnt]=head[u];head[u]=ecnt;du[v]++;}
void dfs(int x,int fa)
{
	for(int i=head[x]; i; i=nxt[i]) if(to[i]!=fa) dfs(to[i],x);
	top=0;ll sum=0;
	for(int i=head[x]; i; i=nxt[i]) if(to[i]!=fa) sta[++top]=v[to[i]],sum+=v[to[i]];
	int len=min(lim[x],du[x]);
	if(len==1)
	{
		int tmp=min(sum,(ll)b[x]);
		v[x]=b[x]-tmp;ans-=tmp;return;
	}
	len=min(len-1,top);
	sum=0;
	for(int i=1; i<=top; i++)
	{
		if(sta[i]>=b[x]&&len>=0) --len,ans-=b[x];
		if(sta[i]<b[x]) sum+=sta[i];
	}
	if(len==-1) return v[x]=0,void();//无力管辖。 
	if(len==0)
	{
		int tmp=min(sum,1ll*b[x]);
		v[x]=b[x]-tmp;ans-=tmp;return;//又是同样的的合并		
	}
	if(len*b[x]>=sum) v[x]=b[x],ans-=sum;//尽量给父亲 
	else if((len+1)*b[x]>=sum) v[x]=(len+1)*b[x]-sum,ans-=sum;//注意还有中间情况,给一点儿子,干完后再给父亲 
	else v[x]=0,ans-=(len+1)*b[x];//弃掉父亲!!
}
int main()
{
	scanf("%d",&T);
	while(T--)
	{
		memset(head,0,sizeof head);
		memset(du,0,sizeof du);ecnt=1;
		memset(v,0,sizeof v);
		scanf("%d",&n);ans=0;
		for(int i=1; i<n; i++) {int a,b;scanf("%d%d",&a,&b);add(a,b);add(b,a);}
		for(int i=1; i<=n; i++) scanf("%d%d",&b[i],&lim[i]),ans+=b[i];
		dfs(1,0);printf("%lld\n",ans);
	}
	return 0;
}
posted @ 2021-11-09 21:53  keepcoder  阅读(43)  评论(0编辑  收藏  举报