欢迎访问 生活随笔!

生活随笔

当前位置: 首页 > 编程资源 > 编程问答 >内容正文

编程问答

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

发布时间:2025/4/16 编程问答 52 豆豆
生活随笔 收集整理的这篇文章主要介绍了 【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[color[i]]。容易想到,这样可以保证不重不漏地统计到所有达到upr[i]的颜色,并且不会重复累加。剩下的套dsu on tree板子即可。

代码:

  • #include <cstdio>  
  • #include <iostream>  
  • #include <cctype>  
  • #include <cstring>  
  • #include <vector>  
  • #define maxn 100010  
  • using namespace std;  
  • template <class T>  
  • void read(T &x) {  
  •     x = 0;  
  •     char ch = getchar();  
  •     while (!isdigit(ch))  
  •         ch = getchar();  
  •     while (isdigit(ch)) {  
  •         x = x * 10 + (ch ^ 48);  
  •         ch = getchar();  
  •     }  
  • }  
  • int n, m, color[maxn], upr[maxn], cnt[maxn], ans[maxn];  
  • struct Query {  
  •     int k, id;  
  • };  
  • vector<Query> Q[maxn];  
  • int head[maxn], top;  
  • struct E {  
  •     int to, nxt;  
  • } edge[maxn << 1];  
  • inline void insert(int u, int v) {  
  •     edge[++top] = (E) {v, head[u]};  
  •     head[u] = top;  
  • }  
  • int size[maxn], son[maxn];  
  • void dfs(int u, int pre) {  
  •     size[u] = 1;  
  •     for (int i = head[u]; i; i = edge[i].nxt) {  
  •         int v = edge[i].to;  
  •         if (v == pre) continue;  
  •         dfs(v, u);  
  •         size[u] += size[v];  
  •         if (size[son[u]] < size[v])  
  •             son[u] = v;  
  •     }  
  • }  
  • int CurSon;  
  • void cal(int u, int pre, int val) {  
  •     if (val == -1)   
  •         --upr[cnt[color[u]]];  
  •     cnt[color[u]] += val;  
  •     if (val == 1)  
  •         ++upr[cnt[color[u]]];  
  •     for (int i = head[u]; i; i = edge[i].nxt) {  
  •         int v = edge[i].to;  
  •         if (v == CurSon || v == pre) continue;  
  •         cal(v, u, val);  
  •     }  
  • }  
  • void dsu(int u, int pre, bool op) {  
  •     for (int i = head[u]; i; i = edge[i].nxt) {  
  •         int v = edge[i].to;  
  •         if (v == pre || v == son[u]) continue;  
  •         dsu(v, u, 0);  
  •     }  
  •     if (son[u])  
  •         dsu(son[u], u, 1), CurSon = son[u];  
  •     cal(u, pre, 1); CurSon = 0;  
  •     for (int i = 0; i < Q[u].size(); ++i)   
  •         ans[Q[u][i].id] = upr[Q[u][i].k];  
  •     if (!op)  
  •         cal(u, pre, -1);  
  • }  
  • int main() {  
  • //  freopen("count.in", "r", stdin);  
  • //  freopen("count.out", "w", stdout);  
  •     read(n), read(m);  
  •     for (int i = 1; i <= n; ++i)   
  •         read(color[i]);  
  •     int u, v;  
  •     for (int i = 1; i < n; ++i) {  
  •         read(u), read(v);  
  •         insert(u, v), insert(v, u);  
  •     }  
  •     int k;  
  •     for (int i = 1; i <= m; ++i) {  
  •         read(v), read(k);  
  •         Q[v].push_back((Query) {k, i});  
  •     }  
  •     dfs(1, 0);  
  •     dsu(1, 0, 1);  
  •     for (int i = 1; i <= m; ++i)  
  •         printf("%d\n", ans[i]);  
  •     return 0;  
  • }  
  • 转载于:https://www.cnblogs.com/TY02/p/11219304.html

    总结

    以上是生活随笔为你收集整理的【CF375D】Trees and Queries——树上启发式合并的全部内容,希望文章能够帮你解决所遇到的问题。

    如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。