【板子】树上启发式合并

给出一棵 𝑛 个节点以 1 为根的树,节点 𝑢 的颜色为 𝑐𝑢,现在对于每个结点 𝑢 询问以 𝑢 为根的子树里一共出现了多少种不同的颜色.

支持离线,考虑预处理后 𝑂(1) 输出答案.

可以发现,每个节点的答案由其子树和其本身得到

我们可以先预处理出每个节点子树的大小和它的重儿子,重儿子同树链剖分一样,是拥有节点最多子树的儿子,这个过程显然可以 𝑂(𝑛) 完成.

我们用 𝑐𝑛𝑡𝑖 表示颜色 𝑖 的出现次数,𝑎𝑛𝑠𝑢 表示结点 𝑢 的答案.

遍历一个节点 𝑢,我们按以下的步骤进行遍历:

  1. 先遍历 𝑢 的轻(非重)儿子,并计算答案,但 不保留遍历后它对 𝑐𝑛𝑡 数组的影响
  2. 遍历它的重儿子,保留它对 𝑐𝑛𝑡 数组的影响
  3. 再次遍历 𝑢 的轻儿子的子树结点,加入这些结点的贡献,以得到 𝑢 的答案.

这样,原本全部遍历两遍,现在轻2遍,重只需要1遍,多层缩减下,整体遍历数量接近Onlogn

复制代码
#include <bits/stdc++.h>
using namespace std;

const int N = 200005;

vector<int> g[N];     // 邻接表
int c[N];             // c[u] 表示节点 u 的颜色
int sz[N];            // 子树大小
int big[N];           // 重儿子
int cnt[N];           // 全局桶:颜色出现次数
int diff;             // 当前子树内不同颜色的数量
int ans[N];           // 最终答案

/**
 * 第一遍 DFS:预处理子树大小和重儿子
 */
void dfs1(int u, int fa) {
    sz[u] = 1;
    for (int v : g[u]) {
        if (v == fa) continue;
        dfs1(v, u);
        sz[u] += sz[v];
        if (sz[v] > sz[big[u]])
            big[u] = v;
    }
}

/**
 * 子树批量更新
 * delta = +1 表示加入,-1 表示删除
 */
void apply(int u, int fa, int delta) {
    int col = c[u];

    // 维护不同颜色数
    if (cnt[col] == 0 && delta == 1) diff++;
    if (cnt[col] == 1 && delta == -1) diff--;

    cnt[col] += delta;

    // 递归处理整棵子树
    for (int v : g[u]) {
        if (v == fa) continue;
        apply(v, u, delta);
    }
}

/**
 * 第二遍 DFS:DSU on Tree 主过程
 * keep = true  表示保留当前子树信息
 * keep = false 表示处理完后清空
 */
void dfs2(int u, int fa, bool keep) {
    // 1. 递归处理所有轻儿子(不保留)
    for (int v : g[u]) {
        if (v == fa || v == big[u]) continue;
        dfs2(v, u, false);
    }

    // 2. 处理重儿子(保留)
    if (big[u])
        dfs2(big[u], u, true);

    // 3. 合并:把轻儿子子树重新加入
    for (int v : g[u]) {
        if (v == fa || v == big[u]) continue;
        apply(v, u, 1);
    }

    // 4. 加入当前节点
    apply(u, fa, 1);

    // 5. 记录答案
    ans[u] = diff;

    // 6. 如果是轻儿子,整体撤销
    if (!keep)
        apply(u, fa, -1);
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n;
    cin >> n;

    // 读入颜色
    for (int i = 1; i <= n; i++)
        cin >> c[i];

    // 读入树边
    for (int i = 1; i < n; i++) {
        int u, v;
        cin >> u >> v;
        g[u].push_back(v);
        g[v].push_back(u);
    }

    // DSU on Tree
    dfs1(1, 0);
    dfs2(1, 0, true);

    // 输出答案
    for (int i = 1; i <= n; i++)
        cout << ans[i] << " \n"[i == n];

    return 0;
}
相关推荐
土豆.exe7 小时前
Fastjson2 2.0.53 哈希碰撞 RCE:从原理到三种打法
算法·哈希算法
黄河123长江7 小时前
有限Abel群的结构()
算法
峥无7 小时前
C++11 深度详解:现代 C++ 基石全梳理
开发语言·c++·笔记
阿米亚波7 小时前
【C++ STL】std::unordered_multimap
开发语言·数据结构·c++·笔记·stl
Jerry8 小时前
LeetCode 92. 反转链表 II
算法
小王C语言8 小时前
【3. 基于 Vibe Coding 的 OJ 平台】. 构建仓库、环境准备、需求梳理、安装依赖
网络·c++
骊城英雄8 小时前
Rust从入门到精通-trait
人工智能·算法·rust
可编程芯片开发9 小时前
基于PI控制算法的pwm直流电机控制系统Simulink建模与仿真
算法
小王C语言9 小时前
【8.进行接口测试】:通过 curl 进行接口自动化测试 / 通过 python 程序进行接口自动化测试
网络·c++
怕浪猫9 小时前
2840亿参数只卖白菜价:DeepSeek V4 Flash 正式版上线,Agent 能力暴涨6倍
人工智能·算法