AtCoder Grant Contest 010 C - Cleaning dfs+逻辑+dp思想

题意:给定一棵树,每个节点的权值为Ai,每次可以挑选两个叶子节点,将两个叶子节点之间路径上的权值-1。问最后能不能使得整棵树的权值为0。


解法:验证解的存在性,就去试着找出一组可行解。这题从最末端的情况开始想就可以很容易想出dp。

随便找个非叶子节点,将其作为整棵树的根,现在对这个有根树进行分析。

首先可以将路径进行归类,对于一个非叶子节点A,经过其的路径可以分为两种情况:

1.经过一个子节点;

2.经过两个子节点。

路径经过两个子节点意味着这条路不用往上走了,即不会影响A的父亲。

路径经过一个子节点意味着需要再向上传递,因为另一个端点不在以A为根的子树里。

接着构思dfs的细节:

1.考虑叶子节点,叶子节点的权值一定得向上传递,因为叶子节点是路径的端点。

2.考虑非叶子节点A‘,已知权值是Ai,设从其儿子节点需要传上来的权值和是tot,其中第一种边贡献了x1,第二种边贡献了x2,其中,x1需要向上传递。容易列出方程:

x1+x2=Ai,2*x2+x1=tot,可以解出x1与x2的值。

接着要做的就是判断x1与x2的合法性:

1.x1与x2均要大于等于0;

2.当x1与x2均大于等于0的时候,要能真正画出这样的情况。观察发现,仅当儿子向上传递的权值分配很畸形的时候才画不出来。设tm为儿子中向上传递权值的最大值,当tm>Ai的时候,就不能画出x1条第一种路径,x2条第二种路径。动手模拟一下就可以看出来。

最后,看一下整棵树的根节点是不是x1等于0,若是的话,证明找到了可行解,因为经过根节点的只能是第二种边。

#include <cstdio>
#include <vector>
#include <cstdlib>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned int uii;
const int maxn=100005;
int n,a,b;
ll A[maxn];
vector<int> tr[maxn];
ll dfs(int u,int fa) {
    if (tr[u].size()==1)
        return A[u];
    ll tot=0,x2=0,x1=0,mm,tm=0;
    for (uii i=0;i<tr[u].size();++i) {
        if (tr[u][i]==fa)
            continue;
        mm=dfs(tr[u][i],u);
        tm=max(tm,mm);
        tot+=mm;
    }
    x2=tot-A[u];
    x1=A[u]-x2;
    if (x1<0||x2<0||A[u]<tm||(x1>0&&fa==-1)) {
        puts("NO");
        exit(0);
    }
    return x1;
}
int main()
{
    scanf("%d",&n);
    for (int i=1;i<=n;++i)
        scanf("%lld",&A[i]);
    if (n==1) {
        puts(A[1]?"NO":"YES");
        return 0;
    }
    for (int i=0;i<n-1;++i) {
        scanf("%d%d",&a,&b);
        tr[a].push_back(b);
        tr[b].push_back(a);
    }
    if (n==2)
        puts(A[1]==A[2]?"YES":"NO");
    else {
        for (int i=1;i<=n;++i)
            if (tr[i].size()>1) {
                dfs(i,-1);
                break;
            }
        puts("YES");
    }
    return 0;
}


  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值