树上炸弹

2026-06-19

问题概要

给你一棵有 \(n\) 个点的树。树上有 \(m\) 个炸弹,第 \(i\) 个炸弹位于节点 \(p_i\),爆炸范围是 \(w_i\),会炸到任何一个距离 \(p_i\) 不超过 \(w_i\) 的点。一个节点上可能有多个炸弹。

求每个节点会被多少个炸弹炸到。

解法

考虑有根树。

如果炸弹只能向下炸,也就是说一个点上的炸弹只能炸到这个点的后代,这问题就好做了。因此,我们把一个炸弹拆成多个只能向下炸的炸弹。比如点 \(u\) 处有一个威力是 \(3\) 的炸弹,我们把这个炸弹拆成四个只能向下炸的炸弹:对于每个 \(k = 0, 1, 2, 3\),点 \(u\) 的第 \(k\) 级祖先处有一个只能向下炸的,威力是 \((3 - k)\) 的炸弹。

当我们把每个炸弹都这样拆解过后,每个点上可能有多个向下炸的炸弹,威力不尽相同。于是我们可以对每个点 \(u\) 算出一个数组 \((\mathrm{cnt}[u][i])_{i=0}^{50}\)\(\mathrm{cnt}[u][i]\) 表示点 \(u\) 处有多少个向下炸的,威力是 \(i\) 的炸弹。

下面来谈如何计算 \(\mathrm{cnt}\) 数组。比如说点 \(u\) 处有一个威力是 \(3\) 的炸弹,\(u\) 的一到三级祖先依次是 \(p, q, r\)。那么上面说的,把这个炸弹拆成四个只能向下炸的炸弹,作用在 \(\mathrm{cnt}\) 数组上就是

cnt[u][3] += 1;
cnt[p][2] += 1;
cnt[q][1] += 1;
cnt[r][0] += 1;

我们不妨将上述操作称为把炸弹上传,类似的,我们还可以把炸弹下传。比如说点 \(u\) 处有 10 个威力是 \(3\) 的,只能向下炸的炸弹,那么它们下传到 \(u\) 的孩子 \(v\) 处就相当于 10 个威力是 \(2\) 的,只能向下炸的炸弹。

cnt[v][2] += cnt[u][3]; 

另一方面,我们注意到,在 \(u\) 的父节点 \(p\) 处的那一个威力是 \(2\) 的,只能向下炸的炸弹,它是不能往 \(u\) 下传的,不然就重复计算了。为了绕过这个限制,我们让点 \(u\) 处威力是 \(1\) 的炸弹减少一个,这样就把从点 \(p\) 传下来的那个威力是 \(1\) 的炸弹抵消了。合起来,把点 \(u\) 处的一个威力是 \(3\) 的炸弹拆成四个只能向下炸的炸弹,作用在 \(\mathrm{cnt}\) 数组上就是

cnt[u][3] += 1; cnt[u][1] -= 1;
cnt[p][2] += 1; cnt[p][0] -= 1;
cnt[q][1] += 1;
cnt[r][0] += 1;

代码

const int maxn = 5e5 + 5;
vector<int> g[maxn];
int pv[maxn];
void dfs(int u, int p) {
    pv[u] = p;
    for (int v : g[u])
        if (v != p)
            dfs(v, u);
}
int cnt[maxn][51];
int ans[maxn];
void dfs2(int u, int p) {
    ans[u] = accumulate(cnt[u], cnt[u] + 51, 0);
    for (int v : g[u])
        if (v != p) {
            for (int i = 1; i <= 50; i++)
                cnt[v][i - 1] += cnt[u][i];
            dfs2(v, u);
        }
}

int main() {
    int n, m;
    cin >> n >> m;
    for (int i = 0; i < n - 1; i++) {
        int u, v;
        cin >> u >> v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    // 找出每个点的父节点
    dfs(1, 0);
    // 上传炸弹
    for (int i = 0; i < m; i++) {
        int u, w;
        cin >> u >> w;
        for (int j = w; j >= 0; j--) {
            cnt[u][j]++;
            if (pv[u]) {
                if (j >= 2)
                    cnt[u][j - 2]--;
                u = pv[u];
            } else break;
        }
    }
    // 下传炸弹
    dfs2(1, 0);
    for (int i = 1; i <= n; i++)
        cout << ans[i] << ' ';
    cout << '\n';
}