Codeforces 620E New Year Tree dfs + 线段树(区间查询+区间置数) + 位运算

题意

  • 给你一棵树n(4e5)个节点,每个节点有一种颜色(60种以下的颜色)
  • 有两种操作,修改一棵子树所有节点的颜色为c
  • 查询,一颗子树下有多少种颜色

思路

  • 先考虑,如果是在一个数组里,每次修改一个区间和查询一个区间怎么做?
  • 基本想法,就是线段树记录下每个区间内的颜色都有哪些,然后就可以归并了
  • 由于只有60种颜色,我们可以用位压缩编码,然后归并的时候,就是按位取或即可
  • 所以,这棵线段树维护的就是一个取或的运算
  • 由于有区间置数,需要标志下移啥的就不多说了~
  • 然后我们的问题,就是把一棵子树的节点映射到一段连续的区间上
  • 我们通过后序周游的方法就可以做到,对于节点u的子树,我们记录它最左的叶子节点对应的数组位置pos_l,后它自己的所在的数组位置pos_r,[pos_l, pos_r]既是我们所要更新或查询的区间

实现

#include <bits/stdc++.h>
using namespace std;
const int maxn = 4e5 + 5;
typedef long long ll;
typedef pair<int,int> pii;
#define pb push_back
#define mp make_pair
#define fi first
#define se second
ll setv[maxn<<2];
ll orv[maxn<<2];

vector<ll> a;
pii id[maxn];
ll col[maxn];
vector<int> g[maxn];
int n,m;

ll build(int u, int l, int r){
    if (l == r){
        return orv[u] = a[l];
    }
    int mid = (l+r)/2, chl = 2 * u + 1, chr = 2 * u + 2;
    return orv[u] = build(chl, l, mid) | build(chr, mid+1, r);
}

int x1,x2;
ll c;
void update(int u, int l, int r){
    if (x1 <= l && x2 >= r){
        orv[u] = c;
        setv[u] = c;
        return;
    }
    if (x1 > r || x2 < l){
        return;
    }
    int mid = (l+r)/2, chl = 2 * u + 1, chr = 2 * u + 2;
    //标志下移 
    if (setv[u] != 0){
        orv[chl] = orv[chr] = setv[chl] = setv[chr] = setv[u];
        setv[u] = 0;
    }
    update(chl, l, mid);
    update(chr, mid+1, r);
    orv[u] = orv[chr] | orv[chl];
}

ll query(int u,int l, int r){
    if (x1 > r || x2 < l){
        return 0;
    }
    if (setv[u] != 0){
        return setv[u];
    }
    if (x1 <= l && x2 >= r){
        return orv[u];
    }
    int mid = (l+r)/2, chl = 2 * u + 1, chr = 2 * u + 2;
    return query(chl, l, mid) | query(chr, mid+1, r);
}

int mark[maxn];
int dfs(int u){
    mark[u] = 1;
    int tmp = -1;
    for (int e=0; e<g[u].size();e++){
        int v = g[u][e];
        if (mark[v] == 1){
            continue;
        }
        if (tmp == -1){
            tmp = dfs(v);
        }
        else{
            dfs(v);
        }

    }
    if (tmp == -1){
        tmp = a.size();
    }
    id[u] = mp(tmp, a.size());
    a.pb(col[u]);
    return tmp;
}


int main(){
    ios::sync_with_stdio(false);
    cin>>n>>m;
    for (int i=1;i<=n;i++){
        int c;
        cin>>c;
        col[i] = (ll)1 << (ll)(c-1);
    }
    for (int i=0;i<n-1;i++){
        int u,v;
        cin>>u>>v;
        g[u].pb(v);
        g[v].pb(u);
    }
    dfs(1);
    //for (int i=1;i<=n;i++){
    //  cout << id[i].fi << "," << id[i].se << endl;
    //}
    build(0, 0, a.size()-1);
    for (int i=0;i<m;i++){
        int t,x,y;
        cin>>t;
        if (t == 1){
            cin>>x>>y;
            x1 = id[x].fi;
            x2 = id[x].se;
            c = (ll)1 << (ll)(y-1);
            update(0, 0, a.size()-1);
        }
        else{
            cin>>x;
            x1 = id[x].fi;
            x2 = id[x].se;
            ll tmp = query(0, 0, a.size()-1);
            int ans = 0;
            while (tmp > 0){
                if (tmp & 1LL){
                    ans++;
                }
                tmp >>= 1LL;
            }
            cout << ans << endl;
        }
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值