【模拟赛】树
贪心。考虑自底向上统计答案。
我们先令答案为 \(\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;
}