【CF375D】Trees and Queries——树上启发式合并

(题面不是来自Luogu)

题目描述

有一个大小为n且以1为根的树,树上每个点都有对应的颜色ci。现给出m次询问v, k,问以v为根的子树中有多少种颜色至少出现了k次。

输入格式

第一行两个数n,m表示树的大小以及询问的次数。

第二行n个数表示树上每个结点的颜色。

接下来的n-1行,每行两个数a, b表示树上的边。

接下来m行,每行两个数v, k表示询问。

输出格式

m行,每行一个数表示第i次询问的答案。

样例输入1

8 5
1 2 2 3 3 2 3 3
1 2
1 5
2 3
2 4
5 6
5 7
5 8
1 2
1 3
1 4
2 3
5 3

样例输出1

2
2
1
0
1

样例输入2

4 1
1 2 3 4
1 2
2 3
3 4
1 1

样例输出2

4

数据范围

2≤n≤100000

1≤m≤100000

1≤ci≤100000

1≤a, b≤n, a≠b

1≤v≤n, 1≤k≤100000

对于其中30%的数据保证n,m≤100且ci≤n

对于其中60%的数据保证n≤5000

  (7:20)今天为了打这个题的正解学了dsu on tree。需要学习的同学请看我的上一篇博客。

  树上启发式合并主要解决的就是这类静态子树统计问题。

  对于这题,我们维护两个数组cnt和upr,分别表示某种颜色出现的数量和出现次数大于某个次数的颜色的种类数。每次暴力累计子树信息时,出现一个颜色先++cnt[color[i]],然后++upr[cnt[color[i]]]。容易想到,这样可以保证不重不漏地统计到所有达到upr[i]的颜色,并且不会重复累加。剩下的套dsu on tree板子即可。

代码:

  1. #include <cstdio>  
  2. #include <iostream>  
  3. #include <cctype>  
  4. #include <cstring>  
  5. #include <vector>  
  6. #define maxn 100010  
  7. using namespace std;  
  8. template <class T>  
  9. void read(T &x) {  
  10.     x = 0;  
  11.     char ch = getchar();  
  12.     while (!isdigit(ch))  
  13.         ch = getchar();  
  14.     while (isdigit(ch)) {  
  15.         x = x * 10 + (ch ^ 48);  
  16.         ch = getchar();  
  17.     }  
  18. }  
  19. int n, m, color[maxn], upr[maxn], cnt[maxn], ans[maxn];  
  20. struct Query {  
  21.     int k, id;  
  22. };  
  23. vector<Query> Q[maxn];  
  24. int head[maxn], top;  
  25. struct E {  
  26.     int to, nxt;  
  27. } edge[maxn << 1];  
  28. inline void insert(int u, int v) {  
  29.     edge[++top] = (E) {v, head[u]};  
  30.     head[u] = top;  
  31. }  
  32. int size[maxn], son[maxn];  
  33. void dfs(int u, int pre) {  
  34.     size[u] = 1;  
  35.     for (int i = head[u]; i; i = edge[i].nxt) {  
  36.         int v = edge[i].to;  
  37.         if (v == pre) continue;  
  38.         dfs(v, u);  
  39.         size[u] += size[v];  
  40.         if (size[son[u]] < size[v])  
  41.             son[u] = v;  
  42.     }  
  43. }  
  44. int CurSon;  
  45. void cal(int u, int pre, int val) {  
  46.     if (val == -1)   
  47.         --upr[cnt[color[u]]];  
  48.     cnt[color[u]] += val;  
  49.     if (val == 1)  
  50.         ++upr[cnt[color[u]]];  
  51.     for (int i = head[u]; i; i = edge[i].nxt) {  
  52.         int v = edge[i].to;  
  53.         if (v == CurSon || v == pre) continue;  
  54.         cal(v, u, val);  
  55.     }  
  56. }  
  57. void dsu(int u, int pre, bool op) {  
  58.     for (int i = head[u]; i; i = edge[i].nxt) {  
  59.         int v = edge[i].to;  
  60.         if (v == pre || v == son[u]) continue;  
  61.         dsu(v, u, 0);  
  62.     }  
  63.     if (son[u])  
  64.         dsu(son[u], u, 1), CurSon = son[u];  
  65.     cal(u, pre, 1); CurSon = 0;  
  66.     for (int i = 0; i < Q[u].size(); ++i)   
  67.         ans[Q[u][i].id] = upr[Q[u][i].k];  
  68.     if (!op)  
  69.         cal(u, pre, -1);  
  70. }  
  71. int main() {  
  72. //  freopen("count.in", "r", stdin);  
  73. //  freopen("count.out", "w", stdout);  
  74.     read(n), read(m);  
  75.     for (int i = 1; i <= n; ++i)   
  76.         read(color[i]);  
  77.     int u, v;  
  78.     for (int i = 1; i < n; ++i) {  
  79.         read(u), read(v);  
  80.         insert(u, v), insert(v, u);  
  81.     }  
  82.     int k;  
  83.     for (int i = 1; i <= m; ++i) {  
  84.         read(v), read(k);  
  85.         Q[v].push_back((Query) {k, i});  
  86.     }  
  87.     dfs(1, 0);  
  88.     dsu(1, 0, 1);  
  89.     for (int i = 1; i <= m; ++i)  
  90.         printf("%d ", ans[i]);  
  91.     return 0;  
  92. }  
原文地址:https://www.cnblogs.com/TY02/p/11219304.html