CF 620E New Year Tree

文章介绍了编程竞赛中的一道题目NewYearTree,涉及树的结构和颜色修改及查询操作。作者提出了使用树剖结合线段树的策略来解决,首先通过树剖给每个节点重新编号,然后用线段树处理颜色计数。在实现过程中,需要注意线段树的push_up、push_down和query、modify函数的细节。最后,作者提到了原始代码的时间复杂度问题,并展示了如何利用bitset优化以避免超时。
摘要由CSDN通过智能技术生成

New Year Tree

题面翻译

  • 给出一棵 n n n 个节点的树,根节点为 1 1 1。每个节点上有一种颜色 c i c_i ci m m m 次操作。操作有两种:
    1. 1 u c:将以 u u u 为根的子树上的所有节点的颜色改为 c c c
    2. 2 u:询问以 u u u 为根的子树上的所有节点的颜色数量。
  • 1 ≤ n , m ≤ 4 × 1 0 5 1\le n,m\le 4\times 10^5 1n,m4×105 1 ≤ c i , c ≤ 60 1\le c_i,c\le 60 1ci,c60

题目描述

The New Year holidays are over, but Resha doesn’t want to throw away the New Year tree. He invited his best friends Kerim and Gural to help him to redecorate the New Year tree.

The New Year tree is an undirected tree with $ n $ vertices and root in the vertex $ 1 $ .

You should process the queries of the two types:

  1. Change the colours of all vertices in the subtree of the vertex $ v $ to the colour $ c $ .
  2. Find the number of different colours in the subtree of the vertex $ v $ .

输入格式

The first line contains two integers $ n,m $ ( $ 1<=n,m<=4·10^{5} $ ) — the number of vertices in the tree and the number of the queries.

The second line contains $ n $ integers $ c_{i} $ ( $ 1<=c_{i}<=60 $ ) — the colour of the $ i $ -th vertex.

Each of the next $ n-1 $ lines contains two integers $ x_{j},y_{j} $ ( $ 1<=x_{j},y_{j}<=n $ ) — the vertices of the $ j $ -th edge. It is guaranteed that you are given correct undirected tree.

The last $ m $ lines contains the description of the queries. Each description starts with the integer $ t_{k} $ ( $ 1<=t_{k}<=2 $ ) — the type of the $ k $ -th query. For the queries of the first type then follows two integers $ v_{k},c_{k} $ ( $ 1<=v_{k}<=n,1<=c_{k}<=60 $ ) — the number of the vertex whose subtree will be recoloured with the colour $ c_{k} $ . For the queries of the second type then follows integer $ v_{k} $ ( $ 1<=v_{k}<=n $ ) — the number of the vertex for which subtree you should find the number of different colours.

输出格式

For each query of the second type print the integer $ a $ — the number of different colours in the subtree of the vertex given in the query.

Each of the numbers should be printed on a separate line in order of query appearing in the input.

样例 #1

样例输入 #1
7 10
1 1 1 1 1 1 1
1 2
1 3
1 4
3 5
3 6
3 7
1 3 2
2 1
1 4 3
2 1
1 2 5
2 1
1 6 4
2 1
2 2
2 3
样例输出 #1
2
3
4
5
1
2

样例 #2

样例输入 #2
23 30
1 2 2 6 5 3 2 1 1 1 2 4 5 3 4 4 3 3 3 3 3 4 6
1 2
1 3
1 4
2 5
2 6
3 7
3 8
4 9
4 10
4 11
6 12
6 13
7 14
7 15
7 16
8 17
8 18
10 19
10 20
10 21
11 22
11 23
2 1
2 5
2 6
2 7
2 8
2 9
2 10
2 11
2 4
1 12 1
1 13 1
1 14 1
1 15 1
1 16 1
1 17 1
1 18 1
1 19 1
1 20 1
1 21 1
1 22 1
1 23 1
2 1
2 5
2 6
2 7
2 8
2 9
2 10
2 11
2 4
样例输出 #2
6
1
3
3
2
1
2
3
5
5
1
2
2
1
1
1
2
3

笨蒟蒻的个人题解

附上题目链接:传送门1传送门2

本题题意很简单:第一个操作就是将 u u u 的颜色全部改成 c c c,第二个操作就是询问 u u u 及其儿孙颜色种类数量。

那么,咱们来想一下,统计儿孙的种类数量,那么这涉及到区间了,那么我们就可以用线段树来做了。但是这题又有点特殊,因为这是在树上,那咋办?那我们就用树剖,给他一个新编号来解决,所以本题的思路就是用树剖 + 线段树。

本题最大的难点就在于,你如何统计一个区间的数量?还有,如何由于每个区间的颜色不同,如何合并呢?

笨蒟蒻没看题解之前,自己想到了一个,那就是用 set 来维护各个区间的儿子的种类和数量,然后合并只要 insert, insert, insert……

话不多说,先献上笨蒟蒻俩小时多写的码……

#include<iostream>
#include<algorithm>
#include<string.h>
#include<string>
#include<vector>
#include<map>
#include<set>
#define int long long
#define fi first
#define se second
#define inf 0x3f3f3f3f3f
using namespace std;
typedef pair<int, int>PII;

const int N = 4e5 + 10, M = N * 2, Mod = 998244353;

int n, m;
struct Tree
{
    int l, r, lz;
    set<int> st;
}tr[N << 2];
int w[N];

int e[M], ne[M], h[N], idx;
int depth[N], fa[N], son[N], siz[N], top[N];
int newid[N], neww[N], cnt;

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

void dfs_1(int u, int f)
{
    fa[u] = f, siz[u] = 1, son[u] = 0;
    if(f != -1) depth[u] = depth[f] + 1;
    for(int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if(j == f) continue;
        dfs_1(j, u);
        siz[u] += siz[j];
        if(siz[son[u]] < siz[j]) son[u] = j;
    }
}

void dfs_2(int u, int f)
{
    top[u] = f;
    newid[u] = ++cnt;
    neww[cnt] = w[u];
    if(son[u]) dfs_2(son[u], f);
    else return;

    for(int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if(j == fa[u]) continue;
        if(j == son[u]) continue;
        dfs_2(j, j);
    }
}

inline void push_down(int u)
{
    auto &root = tr[u], &left = tr[u << 1], &right = tr[u << 1 | 1];
    if(root.lz)
    {
        left.st = {root.lz}, right.st = {root.lz};
        left.lz = root.lz, right.lz = root.lz;
        root.lz = {};
    }
}

inline void push_up(int u)
{
    tr[u].st = {};
    tr[u].st.insert(tr[u << 1].st.begin(), tr[u << 1].st.end());
    tr[u].st.insert(tr[u << 1 | 1].st.begin(), tr[u << 1 | 1].st.end());
}

inline void build(int u, int l, int r)
{
    if(l == r) tr[u] = {l, r}, tr[u].st = {neww[l]}, tr[u].lz = 0;
    else
    {
        tr[u] = {l, r}, tr[u].st = {}, tr[u].lz = 0;
        int mid = l + r >> 1;
        build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
        push_up(u);
    }
}

inline set<int> query(int u, int l, int r)
{
    if(tr[u].l >= l && tr[u].r <= r) return tr[u].st;
    push_down(u);
    set<int> res;
    int mid = tr[u].l + tr[u].r >> 1;
    if(l <= mid)
    {
        set<int> ls = query(u << 1, l, r);
        res.insert(ls.begin(), ls.end());
    }
    if(r > mid)
    {
        set<int> rs = query(u << 1 | 1, l, r);
        res.insert(rs.begin(), rs.end());
    }
    return res;
}

inline void modify(int u, int l, int r, int d)
{
    if(tr[u].l >= l && tr[u].r <= r) tr[u].st = {d}, tr[u].lz = d;
    else
    {
        push_down(u);
        int mid = tr[u].l + tr[u].r >> 1;
        if(l <= mid) modify(u << 1, l, r, d);
        if(r > mid) modify(u << 1 | 1, l, r, d);
        push_up(u);
    }
}

void solve()
{
    cin >> n >> m;
    memset(h, -1, sizeof(h));
    for(int i = 1; i <= n; i++) cin >> w[i];

    for(int i = 1; i < n; i++)
    {
        int x, y;
        cin >> x >> y;
        add(x, y), add(y, x);
    }

    depth[1] = 1;
    dfs_1(1, -1);
    dfs_2(1, 1);

    build(1, 1, n);

    while(m--)
    {
        int op, u, c;
        cin >> op >> u;
        if(op == 1)
        {
            cin >> c;
            modify(1, newid[u], newid[u] + siz[u] - 1, c);
        }
        else cout << query(1, newid[u], newid[u] + siz[u] - 1).size() << endl;
    }
}
signed main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int T = 1;
//    cin >> T;
    while(T--)
        solve();
    return 0;
}

然后呢,笨蒟蒻做的时候还是很艰难的,这里有几点需要注意一下:

1、push_up 这里一定一定一定要先把 s e t set set 置于空,不然就会错,因为题目说了要全改为 c c c(鬼知道我在这里卡了多久……

inline void push_up(int u)
{
    tr[u].st = {};
    tr[u].st.insert(tr[u << 1].st.begin(), tr[u << 1].st.end());
    tr[u].st.insert(tr[u << 1 | 1].st.begin(), tr[u << 1 | 1].st.end());
}

2、query 这里,一定要分开写分开写分开写。就是这个:

set<int> ls = query(u << 1, l, r);
res.insert(ls.begin(), ls.end());

一定一定一定不要写成这样

res.insert(query(u << 1, l, r).begin(), query(u << 1, l, r).end());

千万千万千万不能这么写啊,这样写会出不了结果(别问,问就是我也不知道。

3、push_down 这里,别用 insert 别用 insert 别用 insert,修改不是增加呢

其它的话就没啥好说的了,小心一点写就好了。

真的有能让你这么顺利?樂

这里 TLE 了……

在这里插入图片描述

难受哦……

why?

线段树操作是 l o g n logn logn 级别的,然后有 m m m 次操作?这样就是 O ( m l o g n ) O(mlogn) O(mlogn),正如我一开始写的时候,我以为这样不会爆的,我想了好久菜发现啊, s e t set set 也是占 l o g n … logn\dots logn 那这么算的话就是 O ( m l o g n l o g n ) O(mlognlogn) O(mlognlogn) 了, 4 × 1 0 5 × 20 × 20 4\times10^5\times20\times20 4×105×20×20 就会超。

how,优化?

这里注意一个细节, 1 ≤ c ≤ 60 1\le c\le 60 1c60,那么就可以在这方面做文章了!看了大佬的题解,我恍然大悟,可以用 longlong 状压,这里用一个更妙的解法, b i t s e t bitset bitset 压位(也是看了大佬题解菜知道的……

把每一种颜色当做一位,然后里面开 60 60 60 个空间,每一种颜色代表每一位,那么这样不就是可以很好地表示出每一种颜色了呢?

这和上面的区别是啥?上面是 s e t set set 时间是 O ( l o g n ) O(logn) O(logn),然后这里用 b i t s e t bitset bitset 优化, O ( 1 ) O(1) O(1) 就快了许多,然后其实也很好改,只需要将上面的代码稍微的变动一下就好了。

那么看到这里,也讲完了,献上笨蒟蒻 b i t s e t bitset bitset 优化后的 A C AC AC 代码!! 太难力 orz orz……

AC_Code

#include<iostream>
#include<algorithm>
#include<string.h>
#include<string>
#include<vector>
#include<bitset>
#include<map>
#include<set>
#define int long long
#define fi first
#define se second
#define inf 0x3f3f3f3f3f
using namespace std;
typedef pair<int, int>PII;

const int N = 4e5 + 10, M = N * 2, Mod = 998244353;

int n, m;
struct Tree
{
    int l, r, lz;
    bitset<64> st;
}tr[N << 2];
int w[N];

int e[M], ne[M], h[N], idx;
int depth[N], fa[N], son[N], siz[N], top[N];
int newid[N], neww[N], cnt;

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

void dfs_1(int u, int f)
{
    fa[u] = f, siz[u] = 1, son[u] = 0;
    if(f != -1) depth[u] = depth[f] + 1;
    for(int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if(j == f) continue;
        dfs_1(j, u);
        siz[u] += siz[j];
        if(siz[son[u]] < siz[j]) son[u] = j;
    }
}

void dfs_2(int u, int f)
{
    top[u] = f;
    newid[u] = ++cnt;
    neww[cnt] = w[u];
    if(son[u]) dfs_2(son[u], f);
    else return;

    for(int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if(j == fa[u]) continue;
        if(j == son[u]) continue;
        dfs_2(j, j);
    }
}

inline void push_down(int u)
{
    auto &root = tr[u], &left = tr[u << 1], &right = tr[u << 1 | 1];
    if(root.lz)
    {
        left.st.reset(), left.st.set(root.lz), right.st.reset(), right.st.set(root.lz);
        left.lz = root.lz, right.lz = root.lz;
        root.lz = 0;
    }
}

inline void push_up(int u)
{
    tr[u].st = tr[u << 1].st | tr[u << 1 | 1].st;
}

inline void build(int u, int l, int r)
{
    if(l == r) tr[u] = {l, r}, tr[u].st.set(neww[l]), tr[u].lz = 0;
    else
    {
        tr[u] = {l, r}, tr[u].st.reset(), tr[u].lz = 0;
        int mid = l + r >> 1;
        build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
        push_up(u);
    }
}

inline bitset<64> query(int u, int l, int r)
{
    if(tr[u].l >= l && tr[u].r <= r) return tr[u].st;
    push_down(u);
    bitset<64> res;
    res.reset();
    int mid = tr[u].l + tr[u].r >> 1;
    if(l <= mid)
    {
        bitset<64> ls = query(u << 1, l, r);
        res |= ls;
    }
    if(r > mid)
    {
        bitset<64> rs = query(u << 1 | 1, l, r);
        res |= rs;
    }
    return res;
}

inline void modify(int u, int l, int r, int d)
{
    if(tr[u].l >= l && tr[u].r <= r) tr[u].st.reset(), tr[u].st.set(d), tr[u].lz = d;
    else
    {
        push_down(u);
        int mid = tr[u].l + tr[u].r >> 1;
        if(l <= mid) modify(u << 1, l, r, d);
        if(r > mid) modify(u << 1 | 1, l, r, d);
        push_up(u);
    }
}

void solve()
{
    cin >> n >> m;
    memset(h, -1, sizeof(h));
    for(int i = 1; i <= n; i++) cin >> w[i];

    for(int i = 1; i < n; i++)
    {
        int x, y;
        cin >> x >> y;
        add(x, y), add(y, x);
    }

    depth[1] = 1;
    dfs_1(1, -1);
    dfs_2(1, 1);

    build(1, 1, n);

    while(m--)
    {
        int op, u, c;
        cin >> op >> u;
        if(op == 1)
        {
            cin >> c;
            modify(1, newid[u], newid[u] + siz[u] - 1, c);
        }
        else cout << query(1, newid[u], newid[u] + siz[u] - 1).count() << endl;
    }
}
signed main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int T = 1;
//    cin >> T;
    while(T--)
        solve();
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值