专栏文章

题解:P9233 [蓝桥杯 2023 省 A] 颜色平衡树

P9233题解参与者 3已保存评论 2

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
2 条
当前快照
1 份
快照标识符
@miom9th9
此快照首次捕获于
2025/12/02 21:31
3 个月前
此快照最后确认于
2025/12/02 21:31
3 个月前
查看原文

P9233 [蓝桥杯 2023 省 A] 颜色平衡树 题解

前言

本题目的大部分其他题解使用了DSU on Tree算法,但是这道题目也可以使用线段树合并完成,所以在这里提供一种线段树合并的方法。

正文

题目要求为求一棵树中的子树上的颜色数量信息,并且不带修改,所以可以使用线段树合并。
线段树合并模板
线段树合并的基本思想是在每一个节点上建立动态开点权值线段树,储存每一个节点的信息。再这种情况下,所有叶子节点上的权值线段树所保存的信息即位叶子节点的子树上的信息;而所有其他节点的信息即为其子树下所有节点的信息的和,我们只需要从下至上地将权值线段树的信息合并即可。
CPP
// 存图方式使用前向星

const int N = 2e5 + 5;

int h[N], ne[N], e[N], idx; // 前向星
int root[N]; // 每一个节点的权值线段树的根节点

int merge(int p, int q, int l, int r) // p 和 q 表示合并的两棵树的根节点,l 和 r 表示当前处理的范围
{
    // ...
}

int calc(int u) // 该函数有两个作用: 1.计算 u 的子树的答案; 2.合并 u 的子树
{
    int res = 0;
    
    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i]; // u 为当前节点,j 为正在处理的儿子节点
        res += calc(j); // 先递归处理儿子节点
        
        root[u] = merge(root[u], root[j], 1, 2e5); // 合并线段树
    }
    
    update(res, tr[root[u]].ans); // 因为当前节点的信息已经和所有儿子的信息合并,且所有儿子的信息也和儿子的儿子的信息合并,所以 root[u] 为根的权值线段树的信息为子树 u 的信息
    return res;
}
本题目中信息的维护
那么本题目应该如何处理呢?
因为本题目的要求是求子树中各颜色的数量相等的子树数量,那么可以看出一个子树对答案有贡献当且仅当该子树内各种颜色数量相等。因此我们不妨设权值线段树表示各种颜色的数量,记录最大最小值,如果最大值与最小值等,那么说明当前子树可以贡献答案。
CPP
int merge(int p, int q, int l, int r) // p 和 q 表示合并的两棵树的根节点,l 和 r 表示当前处理的范围
{
    if (!p || !q) return p + q; // 如果不全有节点,那么直接返回有的节点
    if (l == r) 
    {
        tr[p].min = tr[p].max = tr[p].max + tr[q].max; // 因为是叶子节点,所以直接相加
        return p;
    }
    
    int mid = l + r >> 1;
    tr[p].l = merge(tr[p].l, tr[q].l, l, mid), tr[p].r = merge(tr[p].r, tr[q].r, mid + 1, r); // 递归的合并左右儿子
    tr[p].max = max(tr[tr[p].l].max, tr[tr[p].r].max);
    tr[p].min = min(tr[tr[p].l].min, tr[tr[p].r].min); // 上传标记
    return p;
}

void update(int &res, int root)
{
    res += (tr[root].min == tr[root].max); // 当最小值与最大值相等,每个值都相等,产生贡献
}
要注意的细节
权值线段树的空白节点的初始化中,max 字段初始化为 0min 字段初始化为 inf
权值线段树在合并的时候要对线段树叶子节点上的信息相加,在上传标记时要取最大最小值。

完整代码

CPP
#include "bits/stdc++.h"
using namespace std;

#define int long long
const int N = 2e5 + 5, inf = 1e18;
int h[N], ne[N], e[N], idx;
int col[N], root[N], n;

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

struct Node {
    int l, r, min, max;
} tr[N << 5];

int tot;

int createNode()
{
    int cur = ++ tot;
    tr[cur] = {0, 0, inf, 0};
    return cur;
}

void pushup(int u)
{
    tr[u].min = min(tr[tr[u].l].min, tr[tr[u].r].min);
    tr[u].max = max(tr[tr[u].l].max, tr[tr[u].r].max);
}

void insert(int &p, int l, int r, int pos, int delta)
{
    if (!p) p = createNode();
    if (l == r) 
    {
        tr[p].max = tr[p].min = tr[p].max + delta;
        return ;
    }

    int mid = l + r >> 1;
    if (pos <= mid) insert(tr[p].l, l, mid, pos, delta);
    else insert(tr[p].r, mid + 1, r, pos, delta);
    pushup(p);
}

int merge(int p, int q, int l, int r)
{
    if (!p || !q) return p + q;
    if (l == r)
    {
        tr[p].max += tr[q].max;
        tr[p].min += tr[q].min;
        return p;
    }
    int mid = l + r >> 1;

    tr[p].l = merge(tr[p].l, tr[q].l, l, mid), tr[p].r = merge(tr[p].r, tr[q].r, mid + 1, r);
    pushup(p);
    return p;
}

int dfs(int u)
{
    int res = 0;
    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        res += dfs(j);
        merge(root[u], root[j], 1, 2e5);
    }

    return res + (tr[root[u]].max == tr[root[u]].min);
}

int32_t main()
{
    tr[0].min = inf;
    memset(h, -1, sizeof h);
    
    cin >> n;
    for (int i = 1; i <= n; ++i)
    {
        int fa;
        cin >> col[i] >> fa;
        if (fa) add(fa, i);
        insert(root[i], 1, 2e5, col[i], 1);
    }

    cout << dfs(1) << '\n';

    return 0;
}

鸣谢

感谢你阅读到这里,感谢树德中学及石室中学的老师们,感谢树德中学 2024 级的同学们。

评论

2 条评论,欢迎与作者交流。

正在加载评论...