[dsu] codeforces 375D. Tree and Queries

题意:
给出一棵树,1是根节点,n个节点,每个节点有一种颜色。
有m次询问,每次询问给出v k,求以v节点为根的子树中有多少种数量至少为k的颜色,一种颜色的数量就是该颜色的节点的数量。
题解:
离线,回答以v为根的询问时,如果暴力把整棵子树的颜色存进树状数组,复杂度是 O(n2logn)
但是子树信息可以保留到父节点继续使用,如果要保留子树信息的话,容易发现处理两棵子树时,子树间会相互影响,所以先不保留地处理较小的子树,最后保留地处理最大子树。
用树链剖分的思想来说,我们应该保留重儿子的信息,可以证明这样复杂度是 O(nlognlogn)
这种方法对于一类子树上的查询问题有时候还是比较好用的,但是我还不知道中文名叫什么,这个文章讲的十分清楚了,例题也十分精髓。

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+5;
typedef pair<int,int> pii;
vector<int>G[N];
int val[N];
int tree[N];
void update(int p, int v){
    for(p = N-p-1; p < N; p += p&-p) tree[p] += v;
}
int getsum(int p){
    int res = 0;
    for(p = N-p-1; p; p -= p&-p) res += tree[p];
    return res;
}
int sz[N];
void predfs(int rt, int f){
    sz[rt] = 1;
    for(auto& v : G[rt]){
        if(v != f) predfs(v, rt), sz[rt] += sz[v];
    }
}
vector<pii>query[N];
bool bg[N];
int ans[N], col[N];
void add(int rt, int f, int x){
    update(col[val[rt]], -1);
    col[val[rt]] += x;
    update(col[val[rt]], 1);
    for(auto& v : G[rt]){
        if(v != f && !bg[v]) add(v, rt, x);
    }
}
void dfs(int rt, int f, int kp){
    int mx = -1, u = -1;
    for(auto& v : G[rt]){
        if(v != f && sz[v] > mx) mx = sz[v], u = v;
    }
    if(u != -1) bg[u] = 1;
    for(auto& v : G[rt]){
        if(v != f && !bg[v]) dfs(v, rt, 0);
    }
    if(u != -1) dfs(u, rt, 1);
    add(rt, f, 1);
    for(auto& x : query[rt]) ans[x.first] = getsum(x.second);
    if(u != -1) bg[u] = 0;
    if(!kp) add(rt, f, -1);
}
int main(){
    int n, m;
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; ++i) scanf("%d", val+i);
    for(int i = 1; i < n; ++i){
        int a, b;
        scanf("%d%d", &a, &b);
        G[a].push_back(b);
        G[b].push_back(a);
    }
    predfs(1, -1);
    for(int i = 1; i <= m; ++i){
        int v, k;
        scanf("%d%d", &v, &k);
        query[v].push_back(pii(i, k));
    }
    dfs(1, -1, 0);
    for(int i = 1; i <= m; ++i) printf("%d\n", ans[i]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值