树上启发式合并属于暴力的优化,复杂度O(nlogn)

  主要解决的问题特点在于:

    1.对于树上的某些信息进行查询

    2.一般问题的解决不包含对树的修改,所有答案可以离线解决

 

  算法思路:这类问题的特点在于父节点的信息是通过子节点更新而来

  所以如果是暴力解决的话就是对每一个节点往下跑一次图,复杂度在O(n^2)

  因为父节点是有子节点跟新而来,所以我们可以考虑每次保留一部分子节点的信息,将另一部分子节点信息暴力合并得到父节点的信息

  这样的话我们就考虑保留重子节点的信息(子树中子节点数最多的节点),这样我们就可以减少合并次数

  从树链剖分的性质中我们可以得知,一个点到根节点最多不超过logn条路径,所以这样就将算法的复杂度大大降低为O(nlogn)

  引例一:https://www.luogu.com.cn/problem/U41492

  求一颗子树中出现的颜色个数

  

  我们的具体做法如下:

  1.每次先遍历轻子节点,计算轻子节点的信息和答案,但是不保存其信息

  2.遍历重子节点,计算重子节点信息和答案,保存其信息

  3.暴力合并轻子节点信息到重子节点上

  这里比较有疑惑的就是第一和第三步能不能合并到一起,以及为什么在第一步不保存轻子节点的信息。

  其实这是同一个问题,之所以在第一次遍历轻子节点的时候不保存其信息,是为了避免轻子节点的信息对重子节点信息的影响

  换句人话讲:因为我们统计节点颜色的时候,我们是定义一个cnt[]数组进行统计,假设我们第一次遍历轻子节点的时候保留信息

  cnt[]数组记录下当前有的颜色数为totcol = 5,那么在遍历重子节点的时候,如果遍历到一个新的颜色,totcol这个时候会变成6,进而

  在保存数据的时候在重子节点这记录为当前子树内的颜色数为6,这就造成了数据错误,如果不记录轻子节点信息的情况下,当回溯到

  父节点时,轻子节点的信息会不断随着del()函数,totcol减少,然后从0开始遍历重子节点,这样才能正确的计算出重子节点重蕴含的信息。

  

  

 1 # include<iostream>
2 # include<bits/stdc++.h>
3 using namespace std;
4 # define int long long
5 # define endl "\n"
6 const int N = 2e5 + 10;
7 int sz[N], big[N], col[N], l[N], r[N], rnk[N], totdfn;
  /*
     sz[]子树大小
    big[]重儿子
    l[],r[]dfs序列下,子树的边界
    rnk[] dfs序列对应的节点编号
    totdfn dfs序列
  */
8 int ans[N], cnt[N], totcolor;
9 vector<int> g[N];
10 void add(int u) {
11 if (cnt[col[u]] == 0) ++totcolor;
12 cnt[col[u]]++;
13 }
14
15 void del(int u) {
16 cnt[col[u]]--;
17 if (cnt[col[u]] == 0) --totcolor;
18 }
19
20 int getans() {
21 return totcolor;
22 }
23 void dfs0(int u, int fa) {
24 l[u] = ++totdfn;
25 rnk[totdfn] = u;
26 sz[u] = 1;
27 for (int v : g[u]) {
28 if (v != fa) {
29 dfs0(v, u);
30 sz[u] += sz[v];
31 if (!big[u] || sz[v] > sz[big[u]]) big[u] = v;
32 }
33
34 }
35 r[u] = totdfn;
36 }
37 //keep表示是否保留节点信息
38 void dfs1(int u, int fa, bool keep) {
39 for (int v : g[u]) {
40 if (v != fa && v != big[u]) {
41 dfs1(v, u, false);
42 }
43 }//计算轻子节点信息
44 if (big[u]) {
45 dfs1(big[u], u, true);
46 }//计算重子节点信息
47 for (int v : g[u]) {
48 if (v != fa && v != big[u]) {
49 for (int i = l[v]; i <= r[v]; ++i) {
50 add(rnk[i]);
51 }
52 }
53 }
54 add(u);
55 ans[u] = getans();
56 if (keep == false) {
57 for (int i = l[u]; i <= r[u]; ++i) {
58 del(rnk[i]);
59 }
60 }//删除轻子节点信息
61 }
62
63 void solve() {
64 int n;
65 cin >> n;
66 for (int i = 1; i <= n; ++i) g[i].clear();
67 for (int i = 1; i < n; ++i) {
68 int u, v;
69 cin >> u >> v;
70 g[u].push_back(v);
71 g[v].push_back(u);
72 }
73 for (int i = 1; i <= n; ++i) cin >> col[i];
74 dfs0(1, 0);
75 dfs1(1, 0, false);
76 int m;
77 cin >> m;
78 while (m--) {
79 int k;
80 cin >> k;
81 cout << ans[k] << endl;
82 }
83
84 }
85 int tt;
86 signed main() {
87 ios::sync_with_stdio(false);
88 cin.tie(0);
89 cout.tie(0);
90 tt = 1;
91 while (tt--)solve();
92
93
94 return 0;
95 }

  

树上启发式合并(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】Lomset gelral 题解(树上启发式合并)

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

  4. dsu on tree 树上启发式合并 学习笔记

    近几天跟着dreagonm大佬学习了\(dsu\ on\ tree\),来总结一下: \(dsu\ on\ tree\),也就是树上启发式合并,是用来处理一类离线的树上询问问题(比如子树内的颜色种数) ...

  5. 树上启发式合并(dsu on tree)学习笔记

    有丶难,学到自闭 参考的文章: zcysky:[学习笔记]dsu on tree Arpa:[Tutorial] Sack (dsu on tree) 先康一康模板题吧:CF 600E($Lomsat ...

  6. dsu on tree (树上启发式合并) 详解

    一直都没出过算法详解,昨天心血来潮想写一篇,于是 dsu on tree 它来了 1.前置技能 1.链式前向星(vector 建图) 2.dfs 建树 3.剖分轻重链,轻重儿子 重儿子 一个结点的所有 ...

  7. 【Luogu U41492】树上数颜色——树上启发式合并(dsu on tree)

    (这题在洛谷主站居然搜不到--还是在百度上偶然看到的) 题目描述 给一棵根为1的树,每次询问子树颜色种类数 输入输出格式 输入格式: 第一行一个整数n,表示树的结点数 接下来n-1行,每行一条边 接下 ...

  8. CF741D Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths 树上启发式合并(DSU ON TREE)

    题目描述 一棵根为\(1\) 的树,每条边上有一个字符(\(a-v\)共\(22\)种). 一条简单路径被称为\(Dokhtar-kosh\)当且仅当路径上的字符经过重新排序后可以变成一个回文串. 求 ...

  9. hdu6191(树上启发式合并)

    hdu6191 题意 给你一棵带点权的树,每次查询 \(u\) 和 \(x\) ,求以 \(u\) 为根结点的子树上的结点与 \(x\) 异或后最大的结果. 分析 看到子树,直接上树上启发式合并,看到 ...

随机推荐

  1. jbd2的死锁分析

    已经运行多年的jbd2,它还是死锁了 背景:这个是在centos7的环境上复现的,内核版本为3.10.0-957.27.2.el7 下面列一下我们是怎么排查并解这个问题的. 一.故障现象 oppo云内 ...

  2. mustache.js常见用法

    一.mustache基于JS模板引擎,能较为快捷和简单得实现数据得渲染 用法: 1.CDN引入mustache.js,以下是4.0.1版本,有需要可以去github上查询其他版本的代码. (funct ...

  3. 2019 CSP-S Ⅱ 游记

    day0(试机) 第零天,重新打了一遍头文件和读优,熟悉了一下就匆匆走了. day1 T1一看到先把二分打了,然后发现long long要爆,好慌 主要是基础知识不够扎实,不知道unsigned lo ...

  4. 【JDBC】学习路径8-连接池

    为什么是连接池? 第一.受我们硬件资源的限制,我们的一些资源使用时有限制的比如我们的数据库 连接数和线程数.为了摆脱这些限制,我们就使用了池化技术来将这些资源限制在一定范围内. 第二.我们创建和销毁这 ...

  5. Linux虚拟机破解密码步骤

    Linux破解密码 重启系统 到达logo界面快速 按 e 编辑当前条目 将光标移至以 linux 开头的行,此为内核命令行 在UTF-8(RHEL7):ro(RHEL8)后添加 rd.break ( ...

  6. KingbaseES ALTER TABLE 中 USING 子句的用法

    using子句用于在修改表字段类型的时候,进行显示的转换类型. 1.建表 create table t(id integer); 2.插入数据 insert into t select generat ...

  7. 【读书笔记】C#高级编程 第十四章 内存管理和指针

    (一)后台内存管理 1.值数据类型 Windows使用一个虚拟寻址系统,该系统把程序可用的内存地址映射到硬件内存中的实际地址,该任务由Windows在后台管理(32位每个进程可使用4GB虚拟内存,64 ...

  8. [Qt基础内容-08] Qt中MVC的M(Model)

    Qt中MVC的M(Model)简单介绍 Qt有自己的MVC框架,分别是model(模型).view(视图).delegate(委托),这篇文章,简单的介绍以下Qt中有关model(模型)的类以及一些基 ...

  9. JavaScript 之 原型对象、对象原型 —— { }

    JavaScript -- 构造函数 // 构造函数 function Player(name, age) { this.name = name; this.age = age; } JavaScri ...

  10. [开源]React/Vue通用的状态管理框架,不好用你来打我👀

    为了防止被打,有请"燕双鹰"镇楼️‍♀️️‍️‍...o... 话说新冠3年,"状态管理框架"豪杰并起.群雄逐鹿,ReduxToolkit.Mobx.Vuex. ...