[agc010c]Cleaning

前言

这是一个与经典性质有关的题。

题意

一颗点权的树。
每次操作你可以选择两个不同的叶子(度数为1的点),满足其间路径任意点点权不为0,然后把这些点点权-1。
是否能通过任意次操作使得所有点点权为0?

做法

首先判掉n=2那么一定可以找到度数>1的做根。
我们假如把操作这样描述:
一颗点权边权树,初始边权均为0。
每次操作选择两个不同的叶子,将路径上边边权+1。
最后对于每个叶子,其相连所有边边权和等于点权。
而对于非叶子,其相连所有边边权和等于点权的2倍。
容易发现每条边的边权唯一确定。
可以从叶子开始层层往上确定。
确定完后检查根是否满足条件。
现在确定能不能达成。
对于一个节点x,你知道从各个子树上来的路径条数,你要把其中若干在x点匹配,剩余的未匹配条数应该等于x到父亲的边权。
首先x到父亲的边权不能大于其的点权,否则就无解。
因为儿子边的边权和就会小于点权,即使全部保留也不能满足x到父亲的边权。
然后这是经典的匹配问题。
有长度为k的正整数序列a1~k,每次找到两个不同坐标,满足上面的数都大于0,然后同时减1。
问是否存在方案使得若干次操作后变成全0序列。
这个问题也存在一个经典结论,那就是只要最大的那个不超过总和的一半一定可以,否则一定不行。
必要性显然,如果有超过一半的是不可能匹配完的。
充分性构造其实是每次找最大的两个匹配,正确性可以用归纳法证明。
不过本题不同的是,允许我们先给每个ai减一个bi,bi的和就是x到父亲的边权。
由于ai-bi后的和也确定,因此它的一半也确定,我们算出至少要减多少才能使所有ai均不超过一半,再与x到父亲的边权比较即可。

#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const int maxn=100000+10;
int h[maxn],go[maxn*2],nxt[maxn*2],fa[maxn],d[maxn],a[maxn];
int i,j,k,l,t,n,m,tot,root;
bool czy;
void add(int x,int y){
    d[y]++;
    go[++tot]=y;
    nxt[tot]=h[x];
    h[x]=tot;
}
void dfs(int x,int y){
    if (d[x]==1){
        fa[x]=a[x];
        return;
    }
    int t=h[x];
    fa[x]=2*a[x];
    while (t){
        if (go[t]!=y){
            dfs(go[t],x);
            fa[x]-=fa[go[t]];
        }
        t=nxt[t];
    }
}
void dg(int x,int y){
    if (d[x]==1) return;
    int t,l=0;
    if (fa[x]>a[x]) czy=0;
    else{
        t=h[x];
        while (t){
            if (go[t]!=y){
                if (fa[go[t]]>a[x]-fa[x]) l+=(fa[go[t]]+fa[x]-a[x]);
            }
            t=nxt[t];
        }
        if (l>fa[x]) czy=0;
    }
    t=h[x];
    while (t){
        if (go[t]!=y) dg(go[t],x);
        t=nxt[t];
    }
}
int main(){
    scanf("%d",&n);
    fo(i,1,n) scanf("%d",&a[i]);
    fo(i,1,n-1){
        scanf("%d%d",&j,&k);
        add(j,k);add(k,j);
    }
    if (n==2){
        if (a[1]==a[2]) printf("YES\n");else printf("NO\n");
        return 0;
    }
    fo(i,1,n)
        if (d[i]>1) break;
    root=i;
    dfs(root,0);
    if (fa[root]){
        printf("NO\n");
        return 0;
    }
    czy=1;
    dg(root,0);
    if (czy) printf("YES\n");else printf("NO\n");
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值