(这题在洛谷主站居然搜不到……还是在百度上偶然看到的)

题目描述

给一棵根为1的树,每次询问子树颜色种类数

输入输出格式

输入格式:

第一行一个整数n,表示树的结点数

接下来n-1行,每行一条边

接下来一行n个数,表示每个结点的颜色c[i]

接下来一个数m,表示询问数

接下来m行表示询问的子树

输出格式:

对于每个询问,输出该子树颜色数

说明

对于前三组数据,1<=m,c[i]<=n<=100

1<=m,c[i]<=n<=1e5

  本来在学树上启发式合并,偶然看到这个题,就当模板来打了。

  树上启发式合并(dsu on tree)是一种对静态子树节点信息的统计手段。和树上点分治一样,二者都是经过优化的暴力做法,启发式合并的复杂度可以从平方级别降到O(nlogn)。

  启发式合并的思想源自暴力统计子树信息的过程。这题中,某棵树的每个点都有一个颜色,我们要多组询问统计某个点为根的子树内有多少种颜色。

  这不是主席树乱搞嘛

  考虑直接做这个题的话,我们想预处理出每个点的答案,可以直接从每个点出发做一次dfs统计得出答案后再dfs一遍,把它清空。这个显然的做法是O(n^2)的。

  dsu on tree给出的优化策略是,我们想要统计某个点u,可以先遍历完它的轻儿子们,把这些点清空消去对重儿子的影响,后再去统计它的重儿子。这样得到的重儿子的信息我们还可以利用在u上,所以这时候我们保留重儿子的信息,再暴力地把它的轻儿子统计一遍来更新u的答案就可以了。

  直观上看,每次特殊对待(只统计一次)的这个节点选择重儿子显然是最优的。启发式合并的复杂度经过证明可以达到O(nlogn);考虑每个点,这个点会被统计多少次呢?

  1、通过轻边被扫到。根据轻重链的性质,这一步最多会有log次。

  2、被遍历被扫到一次,或者藉由所在重链被扫到一次。是的,我们在合并的过程中遍历重链就像树剖的第二次dfs一样,每条重链只会被扫一次,因此这两种情况是O(1)的。

  dsu on tree的复杂度得到了证明,我们可以愉快地用它来套做题了。需要强调的是,这个算法适用的是“静态统计子树信息”的问题。具体的遍历写法见代码:

  1. #include <iostream>
  2. #include <cstdio>
  3. #include <cstring>
  4. #include <algorithm>
  5. #include <cctype>
  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 head[maxn], top = 1;
  20. struct E {
  21. int to, nxt;
  22. } edge[maxn << 1];
  23. inline void insert(int u, int v) {
  24. edge[++top] = (E) {v, head[u]};
  25. head[u] = top;
  26. }
  27. int n, m;
  28. int cnt[maxn], color[maxn], son[maxn], size[maxn], ans[maxn], NowSon, sum;
  29. void dfs1(int u, int pre) {  //预处理子树大小
  30. size[u] = 1;
  31. for (int i = head[u]; i; i = edge[i].nxt) {
  32. int v = edge[i].to;
  33. if (v == pre) continue;
  34. dfs1(v, u);
  35. size[u] += size[v];
  36. if (size[son[u]] < size[v])
  37. son[u] = v;
  38. }
  39. }
  40. void cal(int u, int pre, int val) {  //计算答案
  41. if (!cnt[color[u]]) ++sum;//在当前树中统计这种颜色
  42. cnt[color[u]] += val;
  43. for (int i = head[u]; i; i = edge[i].nxt) {
  44. int v = edge[i].to;
  45. if (v == pre || v == NowSon)//避开根的重儿子
  46. continue;
  47. cal(v, u, val);
  48. }
  49. }
  50. void dsu(int u, int pre, bool op) {//启发式合并的主体。op为 0表示这次操作由轻边遍历得到,需要清空
  51. for (int i = head[u]; i; i = edge[i].nxt) {
  52. int v = edge[i].to;
  53. if (v == pre || v == son[u])
  54. continue;
  55. dsu(v, u, 0);//轻儿子
  56. }
  57. if (son[u])
  58. dsu(son[u], u, 1), NowSon = son[u];//重儿子
  59. cal(u, pre, 1); NowSon = 0;//重新扫描轻儿子,二次统计
  60. ans[u] = sum;//那么现在的颜色数就是u的信息
  61. if (!op) {//清空当前的统计数
  62. cal(u, pre, -1);
  63. sum = 0;
  64. }
  65. }
  66. int main() {
  67. read(n);
  68. int u, v;
  69. for (int i = 1; i < n; ++i) {
  70. read(u), read(v);
  71. insert(u, v), insert(v, u);
  72. }
  73. for (int i = 1; i <= n; ++i)
  74. read(color[i]);
  75. dfs1(1, 0);
  76. dsu(1, 0, 1);
  77. read(m);
  78. for (int i = 1; i <= m; ++i) {
  79. read(v);
  80. printf("%d\n", ans[v]);
  81. }
  82. return 0;
  83. }

【Luogu U41492】树上数颜色——树上启发式合并(dsu on tree)的更多相关文章

  1. 神奇的树上启发式合并 (dsu on tree)

    参考资料 https://www.cnblogs.com/zhoushuyu/p/9069164.html https://www.cnblogs.com/candy99/p/dsuontree.ht ...

  2. 树上启发式合并 (dsu on tree)

    这个故事告诉我们,在做一个辣鸡出题人的比赛之前,最好先看看他发明了什么新姿势= =居然直接出了道裸题 参考链接: http://codeforces.com/blog/entry/44351(原文) ...

  3. CF600E Lomsat gelral——线段树合并/dsu on tree

    题目描述 一棵树有$n$个结点,每个结点都是一种颜色,每个颜色有一个编号,求树中每个子树的最多的颜色编号的和. 这个题意是真的窒息...具体意思是说,每个节点有一个颜色,你要找的是每个子树中颜色的众数 ...

  4. bzoj3307雨天的尾巴(权值线段树合并/DSU on tree)

    题目大意: 一颗树,想要在树链上添加同一物品,问最后每个点上哪个物品最多. 解题思路: 1.线段树合并 假如说物品数量少到可以暴力添加,且树点极少,我们怎么做. 首先在一个树节点上标记出哪些物品有多少 ...

  5. 【LUOGU???】WD与数列 sam 启发式合并

    题目大意 给你一个字符串,求有多少对不相交且相同的子串. 位置不同算多对. \(n\leq 300000\) 题解 先把后缀树建出来. DFS 整棵树,维护当前子树的 right 集合. 合并两个集合 ...

  6. 【Luogu】P1903数颜色(带修改莫队)

    题目链接 带修改莫队模板. 加一个变量记录现在是第几次修改,看看当前枚举的询问是第几次修改,改少了就改过去,改多了就改回来. 话说我栈用成队列了能过样例?!!!! 从此深信一句话:样例是出题人精心设计 ...

  7. 【CF600E】Lomset gelral 题解(树上启发式合并)

    题目链接 题目大意:给出一颗含有$n$个结点的树,每个节点有一个颜色.求树中每个子树最多的颜色的编号和. ------------------------- 树上启发式合并(dsu on tree). ...

  8. 牛客练习赛47 E DongDong数颜色 (树上启发式合并)

    链接:https://ac.nowcoder.com/acm/contest/904/E 来源:牛客网 DongDong数颜色 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 5242 ...

  9. luoguP3302 [SDOI2013]森林 主席树 启发式合并

    题目链接 luoguP3302 [SDOI2013]森林 题解 本来这题树上主席树暴力启发式合并就完了 结果把lca写错了... 以后再也不这么写了 复杂度\(O(nlog^2n)\) "f ...

随机推荐

  1. react-native 签名

    完成项目时,我们需要将项目打包成一个apk,方便测试以及发布版本. 这时,需要把js代码和图片资源都放进apk中, 并且发布版本还需要签名,今天把这一系列操作记录下来. 一.生成离线bundle包 离 ...

  2. JDK1.8特性(更新中..)

    "15,5,9,17,99,107,47"转List<Long> List<Long> linkCompanyIds = Arrays.asList(&qu ...

  3. DateTimeFormatter接替SimpleDateFormat

    java程序猿经常会碰到的一个问题就是日期格式化.当接收参数中有日期或时间,那么就需要与前端商量好其格式,这边我知道是2种:1.时间戳 2.字符串. 先说一下时间戳,这个形势的参数,Java只需new ...

  4. django配置跨域并开发测试接口

    1.创建一个测试项目 1.1 创建项目和APP django-admin startproject BookManage # 创建项目 python manage.py startapp books ...

  5. Linux 系统编程 学习:02-进程间通信1:Unix IPC(1)管道

    Linux 系统编程 学习:02-进程间通信1:Unix IPC(1)管道 背景 上一讲我们介绍了创建子进程的方式.我们都知道,创建子进程是为了与父进程协作(或者是为了执行新的程序,参考 Linux ...

  6. mysql增删改查语法

    MySQL 关注公众号"轻松学编程"了解更多. MySQL是关系型数据库,对大小写不敏感. MySQL属于关系型数据库, 优点: 复杂查询 可以用SQL语句方便的在一个表以及多个表 ...

  7. ERP的财务凭证的操作与设计--开源软件诞生23

    赤龙ERP财务凭证讲解--第23篇 用日志记录"开源软件"的诞生 [进入地址 点亮星星]----祈盼着一个鼓励 博主开源地址: 码云:https://gitee.com/redra ...

  8. electron 实现文件下载管理器

    文件下载是我们开发中比较常见的业务需求,比如:导出 excel. web 应用文件下载存在一些局限性,通常是让后端将响应的头信息改成 Content-Disposition: attachment; ...

  9. Python如何快速复制序列?

    1 基本用法 把序列乘以一个整数,就会产生一个新序列.这个新序列是原始序列复制了整数份,然后再拼接起来的结果. l=[1,2,3] l2=l * 3 logging.info('l2 -> %s ...

  10. 天啦噜!仅仅5张图,彻底搞懂Python中的深浅拷贝

    Python中的深浅拷贝 在讲深浅拷贝之前,我们先重温一下 is 和==的区别. 在判断对象是否相等比较的时候我们可以用is 和 == is:比较两个对象的引用是否相同,即 它们的id 是否一样 == ...