[Codeforces894D]Ralph And His Tour in Binary Country

本文介绍了一种在完全二叉树上高效求解节点贡献总和的算法,适用于给定节点到根节点距离小于特定阈值的情况。算法通过预处理子树内节点深度的排序和前缀和,结合二分查找技术,实现O(log^2 n)的单次查询复杂度。

题意

给定有nnn个节点的树,节点iii⌊i2⌋\lfloor\frac i2\rfloor2i有距离为LiL_iLi的边。

mmm次询问,每次询问给出A,HA,HA,H

​若某个节点到AAA的距离LLL小于HHH,则会产生H−LH-LHL的贡献,求树上所有点与AAA产生的贡献之和。

n≤106,m≤105n\le10^6,m\le10^5n106,m105


题解

这棵树是一颗完全二叉树.设depu{\rm dep}_udepu表示uuu111的距离。

分别考虑uuu子树内的答案和子树外的答案:

  1. 子树内的答案即
    ∑depv−depu<HH−(depv−depu)=∑depv<H+depu(H+depu)−depv=s(H+depu)−∑depv<H+depudepv\sum_{{\rm dep}_v-{\rm dep}_u<H}H-({\rm dep}_v-{\rm dep}_u)=\sum_{{\rm dep}_v<H+{\rm dep}_u}(H+{\rm dep}_u)-{\rm dep}_v=s(H+{\rm dep}_u)-\sum_{{\rm dep}_v<H+{\rm dep}_u}{\rm dep}_vdepvdepu<HH(depvdepu)=depv<H+depu(H+depu)depv=s(H+depu)depv<H+depudepv
    其中s=∑[depv<H+depu]s=\sum[{\rm dep}_v<H+{\rm dep}_u]s=[depv<H+depu].
    uuu子树内所有的depv{\rm dep}_vdepv放到的一个数组aua_uau中,对aua_uau排序后求其前缀和数组Sumu{\rm Sum}_uSumu
    那么这部分答案就可以通过对aua_uau数组二分得到sss算出来,即s(H+depu)−Sumu[s]s(H+{\rm dep}_u)-{\rm Sum}_u[s]s(H+depu)Sumu[s]
  2. 父亲faufa_ufau及其兄弟节点www.父亲的答案即H−LuH-L_uHLu.兄弟节点www的子树内的节点的答案即
    ∑depv−depw+Lu+Lw<HH−(depv−depw+Lu+Lw)=s′(H+depw−Lu−Lw)−∑depv<H+depw−Lu−Lwdepv\sum_{{\rm dep}_v-{\rm dep}_w+L_u+L_w<H}H-({\rm dep}_v-{\rm dep}_w+L_u+L_w)=s'(H+{\rm dep}_w-L_u-L_w)-\sum_{{\rm dep}_v<H+{\rm dep}_w-L_u-L_w}{\rm dep}_vdepvdepw+Lu+Lw<HH(depvdepw+Lu+Lw)=s(H+depwLuLw)depv<H+depwLuLwdepv
    可以发现上式和1.1.1.中的式子形式相同,故可以统一处理.
  3. 父亲的父亲及父亲的兄弟节点…

由于这是棵完全二叉树,故暴力跳父亲算答案最多跳log⁡n\log nlogn次,算上二分的log⁡n\log nlogn,单次询问复杂度为log⁡2n\log^2nlog2n.
aua_uau可以自底向上通过归并求出,且根据完全二叉树的特性,可以采用非递归的形式求解降低常数。

时间复杂度O(nlog⁡n+mlog⁡2n)O(n\log n+m\log^2n)O(nlogn+mlog2n)

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
typedef long long ll;
int n, m, dep[N], Len[N];
vector<int> a[N];
vector<ll> Sum[N];
inline ll Calc(int u, int H) {
    if (H <= 0 || !a[u].size())
        return 0;
    int s = lower_bound(a[u].begin(), a[u].end(), H) - a[u].begin();
    return s ? (ll)s * H - Sum[u][s - 1] : 0;
}
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 2; i <= n; ++i)
        scanf("%d", Len + i);
    for (int u = 1; u <= n / 2; ++u) {
        int L = u << 1, R = L | 1;
        if (L <= n)
            dep[L] = dep[u] + Len[L];
        if (R <= n)
            dep[R] = dep[u] + Len[R];
    }
    for (int u = n; u; --u) {
        int L = u << 1, R = L | 1;
        a[u].emplace_back(dep[u]);
        if (R <= n)
            merge(a[L].begin(), a[L].end(), a[R].begin(), a[R].end(),
                  back_inserter(a[u]));
        else if (L <= n)
            a[u].insert(a[u].end(), a[L].begin(), a[L].end());
        Sum[u].resize(a[u].size());
        Sum[u][0] = a[u][0];
        for (int i = 1; i < (int)Sum[u].size(); ++i)
            Sum[u][i] = Sum[u][i - 1] + a[u][i];
    }
    for (int u, H; m--;) {
        scanf("%d%d", &u, &H);
        ll Ans = Calc(u, H + dep[u]);
        for (H -= Len[u]; H > 0 && u != 1; H -= Len[u >>= 1])
            Ans += H + Calc(u ^ 1, H - Len[u ^ 1] + dep[u ^ 1]);
        printf("%lld\n", Ans);
    }
    return 0;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值