Codeforces 348B 思维

本文探讨了如何在以111为根的树中,通过合理删除苹果使子树权值均衡,利用递归和最大公约数原理找到平衡条件,揭示了从叶子节点向上分配权值的关键。解决过程涉及动态规划和最小公倍数计算。
题意:

给出一颗以111为根的树,在叶子节点上长有苹果,定义子树的权值为该子树苹果数量之和,一颗平衡的树为对于树上每个点,他的每棵子树权值相同。求至少去掉多少苹果,可以使子树平衡。

方法:

如果从小子树到大子树,来平衡子树的贡献的话,是难以平衡的,譬如现在有权值为3,4,53,4,53,4,5的子树,要平衡他们,一个想法就是取最小值,但是对于第二颗子树,只删去一个苹果的话,如果删除的地方有兄弟节点,那么就不平衡了,难以操作。

正解思路是设xxx为最后平衡下来整棵树最大的权值和,那么因为子树权值一样,每个点的权值都要平分给他的子树们,设tot[i]tot[i]tot[i]iii结点的儿子数,那么根的儿子们的权值都应该是xtot[1]\frac{x}{tot[1]}tot[1]x,按照这个思路下去,一直到叶子结点uuu,一路平分给儿子们,必然是这样的形式xtot[a]∗tot[b]∗...∗tot[z]\frac{x}{tot[a]*tot[b]*...*tot[z]}tot[a]tot[b]...tot[z]x,其中a→za\rightarrow zaz即他的所有祖先们,不妨设分母为KKK,因为一定能平分下来,那么xxx一定是KKK的倍数

,且删除数量之后,一定有xK≤v[u]\frac{x}{K}\leq v[u]Kxv[u],即x≤K∗v[u]x\leq K*v[u]xKv[u],删除之后一定小于等于原来的嘛。每个叶子都有这样的等式限制,那么全部的限制就是一定是所有叶子的KKK的共同倍数,即lcm(K1,K2....)lcm(K_{1},K_{2}....)lcm(K1,K2....)的倍数,又一定≤min(K∗v[1],K∗v[2]....)\leq min(K*v[1],K*v[2]....)min(Kv[1],Kv[2]....),这里的(1,2,...)(1,2,...)(1,2,...)代表所有的叶子节点,那么就能求到xxx的最大值,所以要删去的数目就是tot−xtot-xtotx了,tottottot是所有点的权值总和

at all,做题的时候只从树形dp的小规模到大规模的角度思考,却忽略了平分这一点关键,一直做不出来

tips: 如果计算到某个地方的K>totK>totK>tot了,那么就需要全删掉了,为了防止爆ll,直接输出tottottot

#include<bits/stdc++.h>
#define ll long long
using namespace std;

struct way
{
    int to,next;
}edge[200005];
int head[100005],en;

void add(int u,int v)
{
    edge[++en].to=v;
    edge[en].next=head[u];
    head[u]=en;
}

int v[200005],n;
ll ans,top=0x3f3f3f3f3f3f3f,tot[100005],totlcm=1;

void dfs1(int u,int fa)
{
    for(int i=head[u];i;i=edge[i].next)
    {
        int v=edge[i].to;
        if(v==fa) continue;
        tot[u]++; dfs1(v,u);
    }
}

ll __lcm(ll a,ll b)
{
    return a/__gcd(a,b)*b;
}

void dfs(int u,int fa,ll k)
{
    if(k>ans)
    {
        printf("%lld\n",ans);
        exit(0);
    }
    for(int i=head[u];i;i=edge[i].next)
    {
        int v=edge[i].to;
        if(v==fa) continue;
        dfs(v,u,k*tot[u]);
    }
    if(tot[u]==0)
    {
        totlcm=__lcm(totlcm,k);//保存到达叶子节点所有K的lcm
        top=min(top,k*v[u]);//记录上界的最小值
    }
}   

int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&v[i]);
        ans+=v[i];
    }
    for(int i=1;i<n;i++)
    {
        int u,v; scanf("%d%d",&u,&v);
        add(u,v); add(v,u);
    }
    dfs1(1,0); dfs(1,0,1ll); 
    cout<<ans-top/totlcm*totlcm;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值