芙莉莲的魔导书

芙莉莲的魔导书

题目描述

按照芙莉莲的研究,人类魔法按照难度构成一棵树,树根为 $1$ 号节点,人类对魔法的理解随时在演进,因此芙莉莲会对这棵树做并修改标记。

树上编号为 $i$ 的节点有标记 $a_i$,初始均为 $0$。

你需要维护两种操作:

  • 芙莉莲会指定编号为 $x$ 的节点,将以这个节点为根的子树的所有非叶节点标记改为 $y$,叶子节点标记改为 $z$。
  • 芙莉莲查询标记为 $x$ 的节点有多少个,你需要给出回答。

输入描述:

本题有多组数据,输入数据第一行一个正整数 $T \left(1 \leq T \leq 10^5\right)$,表示数据组数。

对于每组数据:

第一行包含两个整数 $n \left(1 \leq n \leq 10^5\right)$ 和 $q \left(1 \leq q \leq 10^5\right)$,其中 $n$ 表示节点数量,$q$ 表示操作数量。

接下来 $n-1$ 行,每行包含两个整数 $u,v \left(1 \leq u,v \leq n\right)$,表示树上的一条边。

接下来 $q$ 行每行包含 $2$ 或 $4$ 个整数,表示一个操作,具体如下:

  • $\verb|1 x y z| \left(1 \le x \le n, 0 \le y,z \le 10^9\right)$:指定编号为 $x$ 的节点,将以这个节点为根的子树的所有非叶节点标记改为 $y$,叶子节点标记改为 $z$。
  • $\verb|2 x| \left(0 \le x \le 10^9\right)$:查询标记为 $x$ 的节点有多少个。

保证所有数据中 $n,q$ 之和均不超过 $10^5$。

输出描述:

对于每个 $\verb|2|$ 操作,输出一个数作为回答。

示例1

输入

1
5 7
1 2
1 3
2 4
1 5
1 4 1 0
2 3
2 3
2 0
1 1 3 0
2 3
2 0

输出

0
0
5
2
3

 

解题思路

  其实这题的题解早该写了,只不过一直偷懒拖到了现在。主要还是因为最近半年写的东西没什么人看了,写作热情也随之逐渐消退。可能现在大部分人有问题都直接问 LLM 了吧,都什么年代了还在搜传统博客.jpg

  回到题目本身,由于每次操作都是对整棵子树进行修改,因此一个自然的思路是对整棵树求出 DFS 序,这样任意一棵子树就对应到 DFS 序上的一段连续区间,子树操作也就转化为对连续序列的操作。然而,本题的操作需要对子树中的非叶节点与叶子节点分别赋值,这两类节点在 DFS 序中并不连续。一个自然的想法是,分别针对非叶子节点和叶子节点构建两套独立的 DFS 序(然而赛时我根本就想不到 (T▽T)),从而将操作拆解为对两个连续区间分别进行赋值。对于区间赋值,很容易想到用线段树来维护,但本题的查询是统计整棵树中某个值的节点数量,相当于求整个序列中某个值的总数,这就很难用传统的线段树来高效维护了。

  实际上,本题可以视为 [HAOI2014] 贴海报 的加强版,原题同样涉及区间赋值,而本题将操作次数 $m$ 提升至 $10^5$,并在操作过程中随时进行查询,而非所有操作结束后一次性统计。原题除了线段树外,还可以用 珂朵莉树,一种专门用于处理区间赋值的暴力优化。对于加强版,珂朵莉树同样适用,而且实现更为简洁。如果读者尚未了解过珂朵莉树,可以先尝试用珂朵莉树解决 [HAOI2014] 贴海报,便能很容易触类旁通想到本题的解法。由于赛时我完全没学过珂朵莉树,所以当时硬是卡了一个多小时都不会做,纯浪费时间罚坐说是。

  回到本题,对于区间赋值操作,我们可以在 DFS 序上用珂朵莉树轻松维护。此外,再维护一个 std::map 用来记录当前整个序列中每个值的出现次数。在珂朵莉树的 assign 操作中,会涉及旧区间的删除与新区间的插入,只需在此时同步更新对应值的出现次数即可。这样,查询某种标记的出现次数就可以直接在 std::map 中 $O(\log n)$ 得到答案。此外,由于只存在区间赋值操作,每次 assign 操作中的两次 split 至多增加两个区间,且每次至少删除一个区间并最终插入恰好一个区间,因此每次区间赋值至多净增两个区间。这就保证了所有操作中插入与删除区间的总数量级是 $O(m)$ 的,总的时间复杂度为 $O(m \log{\log{n}})$。

  解决了核心维护逻辑后,剩下的问题就是如何分别求出非叶子节点与叶子节点的 DFS 序了。我们可以为这两类节点分别维护计数器 $c_1$ 和 $c_2$。在初次遍历到某个节点时,当前计数器的值就代表该节点在对应 DFS 序中的下标,同时也是以其为根的子树区间的左端点。在递归遍历其子节点之前,需要根据当前节点是叶子还是非叶子来将 $c_1$ 或 $c_2$ 加 $1$,以表示 DFS 序中下一个可用位置的下标。当递归返回到该节点时,当前 $c_1$ 或 $c_2$ 的值减 1,便是该子树在对应 DFS 序中的右端点。最后还需要留意判断叶子节点的细节,尤其是根节点的特殊情况。并且在本题中,若整棵树只有一个节点,则规定该节点为叶子。

  AC 代码如下,时间复杂度为 $O(m \log{\log{n}})$:

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

typedef long long LL;

const int N = 1e5 + 5, M = N * 2;

int h[N], e[M], ne[M], idx;
int l1[N], r1[N], l2[N], r2[N], c1, c2;
map<int, int> odt1, odt2, cnt1, cnt2;

void add(int u, int v) {
    e[idx] = v, ne[idx] = h[u], h[u] = idx++;
}

void dfs(int u, int p) {
    l1[u] = c1;
    l2[u] = c2;
    if (u == 1 && h[u] != -1 || u > 1 && ne[h[u]] != -1) c1++;
    else c2++;
    for (int i = h[u]; i != -1; i = ne[i]) {
        int v = e[i];
        if (v == p) continue;
        dfs(v, u);
    }
    r1[u] = c1 - 1;
    r2[u] = c2 - 1;
}

void split(map<int, int> &odt, int x) {
    odt[x] = prev(odt.upper_bound(x))->second;
}

void assign(map<int, int> &odt, map<int, int> &cnt, int l, int r, int c) {
    if (l > r) return;
    split(odt, l), split(odt, r + 1);
    auto it = odt.find(l);
    while (it->first <= r) {
        cnt[it->second] -= next(it)->first - it->first;
        it = odt.erase(it);
    }
    odt[l] = c;
    cnt[c] += r - l + 1;
}

void solve() {
    int n, m;
    cin >> n >> m;
    idx = 0;
    memset(h, -1, n + 1 << 2);
    for (int i = 0; i < n - 1; i++) {
        int u, v;
        cin >> u >> v;
        add(u, v), add(v, u);
    }
    c1 = c2 = 0;
    dfs(1, 0);
    odt1 = {{0, 0}, {c1, 0}};
    odt2 = {{0, 0}, {c2, 0}};
    cnt1 = {{0, c1}};
    cnt2 = {{0, c2}};
    while (m--) {
        int op, x;
        cin >> op >> x;
        if (op == 1) {
            int y, z;
            cin >> y >> z;
            assign(odt1, cnt1, l1[x], r1[x], y);
            assign(odt2, cnt2, l2[x], r2[x], z);
        }
        else {
            cout << cnt1[x] + cnt2[x] << '\n';
        }
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t;
    cin >> t;
    while (t--) {
        solve();
    }
    
    return 0;
}

 

参考资料

  【题解】2026年深圳大学-腾讯云程序设计竞赛:https://pan.baidu.com/s/1CUIeB4yfIG2RVs0FCYnnEw?pwd=yz4f

posted @ 2026-04-23 17:14  onlyblues  阅读(9)  评论(0)    收藏  举报
Web Analytics