最后一场排位赛题解

2022 SDU Trial Problem A:

简单题意:

给定一棵有根树,将此树划分成 θ 条从某个点到它的祖先的链,每条链的长度和权重存在限制,问 θ 的最小值。

我是怎么想的:

这是一个比较熟悉的问题,尽管题目有长度和权重限制。

我的第一个想法是,将所有点按照深度降序排列,依次遍历。如果遍历到一个未被标记的节点,那就从此处开始一步一步往上走,把沿途的点给标记上,如果遇到一个已经标记过的点或者链的长度或者重量超过了限制,那就停止往上走。然后遍历下一个节点。这是一个很朴素的剖分方法,相信很多人第一感都是这样的。由于每个点只能被标记一次,所以这个暴力方法的复杂度实际上是 O ( n ) O(n) On的。

这样对吗?答案是否定的。
考虑下面这张图:

反例

如果L=2,W=1,那么,按照我们的算法,我们会得到三条链:
4-2
3
1
明明节点3是可以往上走的,但是因为2已经被标记过了,所以3错失了往上走的机会。

修正后的算法:
修正的办法很简单,我们可以一直往上跳到不能再跳为止。但是,这种做法的时间复杂度是 O ( n 2 ) O(n^2) O(n2) 的,显然无法AC。于是我们想到了一个加速树上跳跃的经典算法:
Binary Lifting On Tree (树上倍增)。

只要知道每个点的父亲标号、每个点跳到父亲的花费,就能预处理出它的K级父亲的标号、跳到K级父亲的花费。

假设计算每个点能跳到的最高位置的工作已经解决了,那我们如何统计答案呢?
这就是树形dp的问题了(不会的同学回去想一下,这个东西一两句说不清楚)。

为什么我一直WA:

这是我错误的树上跳跃实现:

int m=x,d=deep[m];
    for(int j=19;j>=0;j--){
        if(d&(1<<j)){
            if(now+val[m][j]<=up){
                now=now+val[m][j];
                m=fa[m][j];
            }
        }
    }
    return m;

这份代码一眼看去就是经典的树上倍增,d是深度,
从高位到低位检查d的二进制位,如果可以往上跳那就往上跳。但是,在存在限制条件时,检查2进制位并不是一个周到的算法。
下面改进的算法才work:

int m=x,d=deep[m];
    for(int j=19;j>=0;j--){
        if(d>=(1<<j)){
            if(now+val[m][j]<=up){
                now=now+val[m][j];
                m=fa[m][j];
                d-=1<<j;
            }
        }
    }
    return m;

我相信一个脑袋正常的人都会按照正解这样来写,因为这样的意思才是“能跳就跳”。
为什么我会写成最上面那种逻辑呢?因为经典的树上倍增就是这样实现的,写习惯了。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

组合,我有特殊的计数技巧

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值