B.清扫
考虑从叶子节点往上推
首先可以发现的几个性质
- 子树内需要消除的数,要么通过子树根节点 “发送” 到上面(只经过子树内一个叶节点),要么通过自己的叶节点解决
- 对于子树内既不是根也不是叶节点的节点,节点上的值只能由这一支路的叶节点消除,所以如果他节点上的值和下面节点 “发送” 上来的值不相等时,无解
然后考虑怎么去计算子树根节点向上的 “发送” 数量
设这个数量为 \(k\),子树各支路 “发送” 到子树根节点的数量总和为 \(s\),子树根节点的权值为 \(a\),可以发现
- 子树内解决会为 \(s\) 带来 \(-2\) 的贡献
- “发送” 出去会为 \(s\) 带来 \(-1\) 的贡献
- 无论何种操作,每次操作总会给 \(a\) 带来 \(-1\) 的贡献
因此:
\[k+2(a-k)=s \]由此可以唯一确定 \(k\),将这个值继续往上传即可
例图
这是一个 \(k=0\) 的图
需要注意的
- 由题有 \(0\le k\le a\),否则无解
- 我们在上述式子中只判断了数量关系,但实际上,由于子树内必须要选择不同的叶子配对,因此可能会出现有叶子无法使用的情况,此时是无解的
这种情况的判断方式:\(s\) 个数的最大配对次数是 \(s/2\),其次,每个点不能和自己匹配,所以有一个限制是 \(s-\max\)(其中 \(\max\) 是子树传入的最大值),这两个值取 \(\min\),然后和子树的值总和比较,小于说明有剩余,报告无解
- 注意 \(n=2\) 的情况需要特判
#include<bits/stdc++.h>
using namespace std;
int n;
int a[100001];
vector<int>e[100001];
int degree[100001];
bool flag=false;
int dfs(int now,int last){
// cout<<"dfs "<<now<<" "<<last<<endl;
int res=0,cnt=0,maxn=0;
for(int i:e[now]){
if(i!=last){
cnt++;
int r=dfs(i,now);
res+=r;maxn=max(maxn,r);
if(flag) return 0;
}
}
if(cnt==0){
return a[now];
}
if(cnt==1){
if(res!=a[now]){
flag=true;
return 0;
}
return res;
}
if(res<a[now] or res>2*a[now]){
flag=true;
return 0;
}
if(min(res/2,res-maxn)<res-a[now]){
flag=true;
return 0;
}
return 2*a[now]-res;
}
int main(){
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
// freopen("b.in","r",stdin);
scanf("%d",&n);
for(int i=1;i<=n;++i){
scanf("%d",&a[i]);
}
if(n==2){
cout<<(a[1]==a[2]?"YES":"NO")<<'\n';
return 0;
}
for(int i=1;i<=n-1;++i){
int x,y;scanf("%d %d",&x,&y);
e[x].push_back(y);
e[y].push_back(x);
degree[x]++;degree[y]++;
}
for(int i=1;i<=n;++i){
// cout<<i<<" "<<degree[i]<<"::"<<endl;
if(degree[i]!=1){
if(dfs(i,0)){
flag=true;
}
break;
}
}
cout<<(flag?"NO":"YES");
}
标签:10,子树内,int,43,CSP,发送,树根,节点
From: https://www.cnblogs.com/HaneDaCafe/p/18451391