[探究] dsu on tree,一类树上离线问题的做法
dsu on tree.
\(\rm 0x01\) 前言\(\&\)技术分析
\(\bold{dsu~on~tree}\),中文别称“树上启发式合并”(虽然我并不承认这种称谓),大概是一种优雅的暴力,并且跟\(dsu\)毫无关系。于是我打算叫他\(\bold{Elegantly~Direct~Counting~on~Tree}\),“优雅的树上暴力统计”。
严格来说,\(\bold {EDCT}\)解决的问题范围并不广泛:
1、维护子树信息;
2、不能带修改操作。
但这仍然掩盖不住这种算法自带的有趣的气质。笔者认为,这种算法虽然是个暴力,但是其中的技术含量还是不低的,代码也不是那么的浅显易懂,算是一个比较考察应用能力的算法。
然后来看技术分析。
首先,假设我们有这样一个问题:
给定一棵有根树树,每个点有一个信息。现在考虑求出每个点子树内的规定的有效信息数量。
\(n,q\leq 5\cdot1e5\)
一般而言这样的题是可以上莫队的,但是便于展开就开到了\(500,000\)。
考虑\(n^2\)的暴力,即对每个节点都扫一遍子树。很容易发现这样是浪费的,因为会算重。我们考虑怎么对这棵树进行划分才能高效计算。
考虑一种合适的划分方案。结合轻重链剖里面的结论,可以知道,在轻重链剖后,一个点到根不会超过\(\log n\)条轻边。所以如果对于每个点,假设我们只计算他对轻祖先的贡献,需要至多\(\log n\)次就可以解决;同时我们考虑重儿子,每个点至多会被当成一次重儿子,所以假设我们只计算他对父亲的贡献,那么至多\(1\)次就可以解决。所以最后的复杂度是\(O(n\log n)\)的。
现在考虑实现层面,其实是一种分治的思想。我们考虑首先分治\(u\)的轻儿子并清除轻儿子的贡献,然后暴力计算重儿子,然后暴力计算一整棵子树的贡献。首先第一步中清除贡献是必要的,因为分治出来的几个子问题相互独立,所以必须要独立计算。之后是重儿子,由于重儿子至多有一个,所以可以直接计算而不会影响其他状态。最终再暴力一遍计算轻儿子的贡献。
所以这样就解决了维护树上信息的问题,复杂度\(n\log n\)。
\(0x02\) 入门题目选整
感觉大部分blog找的题目都很不清真233
\(\rm Task1\) \(\rm Cf600E\) Lomsat gelral
一句话题意/
一棵树有n个结点,每个结点都是一种颜色,每个颜色有一个编号,求树中每个子树的最多的颜色编号的和。
考虑套\(\rm EDCT\)的板子:
void dfs(int u, int fa){
sz[u] = 1 ;
for (int k = head[u] ; k ; k = E[k].next){
if (to(k) == fa) continue ;
dfs(to(k), u), sz[u] += sz[to(k)] ;
if (sz[to(k)] > sz[son[u]]) son[u] = to(k) ;
}
}
void dfs(int u, int fa, int mk){
for (int k = head[u] ; k ; k = E[k].next){
if (to(k) == fa || to(k) == son[u]) continue ;
dfs(to(k), u, 0) ;
}
if (son[u]) dfs(son[u], u, 1), vis[son[u]] = 1 ;
calc(u, fa, 1) ; ans[u] = res ; if (son[u]) vis[son[u]] = 0 ;
if (!mk) calc(u, fa, -1), res = 0, max_cnt = 0 ;
}
然后就是最后的calc函数怎么写了。考虑我们最暴力的做法是什么?就是把每个颜色统计一遍。所以这么写就OK了:
void calc(int u, int fa, int mk){
buc[clr[u]] += mk ;
if (mk > 0 && buc[clr[u]] >= max_cnt){
if (buc[clr[u]] > max_cnt)
res = 0, max_cnt = 1ll * buc[clr[u]] ;
res += 1ll * clr[u] ;
}
for (int k = head[u] ; k ; k = E[k].next){
if (to(k) == fa || vis[to(k)]) continue ;
calc(to(k), u, mk) ;
}
}
\(\rm Task2 ~Cf570D\) Tree Requests
一句话题意:
给定一个以1为根的n个节点的树,每个点上有一个字母\((a-z)\),每个点的深度定义为该节点到1号节点路径上的点数.每次询问\((a,b)\)查询以\(a\)为根的子树内深度为\(b\)的节点上的字母重新排列之后是否能构成回文串.
这种应该就是比较裸的\(\rm EDCT\)。有一步转化需要学会构造,即我们令一个字符的权值\(val(x)=\text{1<<(x-'a')}\),那么对与一个串\(\rm S\),我们令\(k=\rm{Xor}_{i=1}^n\it val\rm( S[i])\),那么重排之后可以构成回文串\(\Longleftrightarrow\) \(size(k)\leq 1\),其中\(size(\rm S)\)指集合\(\rm S\)内的元素个数,也就是二进制表示中\(1\)的个数。所以也是,直接爆算就可以了。
void calc(int u, int fa){
buc[dep[u]] ^= (1 << base[u]) ;
for (int k = head[u] ; k ; k = E[k].next)
if (to(k) != fa && !vis[to(k)]) calc(to(k), u) ;
}
int getl(int x){
int ret = 0 ;
while (x) ret += (x & 1), x >>= 1 ;
return (bool)(ret <= 1) ;
}
void del(int u, int fa){
buc[dep[u]] = 0 ;
for (int k = head[u] ; k ; k = E[k].next)
if (to(k) != fa && !vis[to(k)]) del(to(k), u) ;
}
void dfs(int u, int fa, int mk){
for (int k = head[u] ; k ; k = E[k].next)
if (to(k) != fa && to(k) != son[u]) dfs(to(k), u, 0) ;
if (son[u]) dfs(son[u], u, 1), vis[son[u]] = 1 ;
calc(u, fa) ;
for (int k = 0 ; k < qs[u].size() ; ++ k)
ans[u].pb(getl(buc[qs[u][k]])) ;
vis[son[u]] = 0 ; if (!mk) del(u, fa) ;
}
哦,漏说一点,这题需要边查询边记录询问的答案,所以需要把询问离线起来一起飞(?)。
[探究] dsu on tree,一类树上离线问题的做法的更多相关文章
- 总结-DSU ON TREE(树上启发式合并)
考试遇到一道题: 有一棵n个点的有根树,每个点有一个颜色,每次询问给定一个点\(u\)和一个数\(k\),询问\(u\)子是多少个不同颜色节点的\(k\)级祖先.n<=500000. 显然对每一 ...
- dsu on tree:关于一类无修改询问子树可合并问题
dsu on tree:关于一类无修改询问子树可合并问题 开始学长讲课的时候听懂了但是后来忘掉了....最近又重新学了一遍 所谓\(dsu\ on\ tree\)就是处理本文标题:无修改询问子树可合并 ...
- dsu on tree (树上启发式合并) 详解
一直都没出过算法详解,昨天心血来潮想写一篇,于是 dsu on tree 它来了 1.前置技能 1.链式前向星(vector 建图) 2.dfs 建树 3.剖分轻重链,轻重儿子 重儿子 一个结点的所有 ...
- DSU on Tree浅谈
DSU on tree 在之前的一次比赛中,学长向我们讲了了这样一个神奇的思想:DSU on tree(树上启发式合并),看上去就非常厉害--但实际上是非常暴力的一种做法;不过暴力只是看上去暴力,它在 ...
- dsu on tree详解
这个算法还是挺人性化的,没有什么难度 就是可能看起来有点晕什么的. 大体 思想是 利用重链刨分来优化子树内部的查询. 考虑一个问题要对每个子树都要询问一次.我们暴力显然是\(n^2\)的. 考虑一下优 ...
- dsu on tree 树上启发式合并 学习笔记
近几天跟着dreagonm大佬学习了\(dsu\ on\ tree\),来总结一下: \(dsu\ on\ tree\),也就是树上启发式合并,是用来处理一类离线的树上询问问题(比如子树内的颜色种数) ...
- 树上启发式合并(dsu on tree)学习笔记
有丶难,学到自闭 参考的文章: zcysky:[学习笔记]dsu on tree Arpa:[Tutorial] Sack (dsu on tree) 先康一康模板题吧:CF 600E($Lomsat ...
- 【Luogu U41492】树上数颜色——树上启发式合并(dsu on tree)
(这题在洛谷主站居然搜不到--还是在百度上偶然看到的) 题目描述 给一棵根为1的树,每次询问子树颜色种类数 输入输出格式 输入格式: 第一行一个整数n,表示树的结点数 接下来n-1行,每行一条边 接下 ...
- 树上统计treecnt(dsu on tree 并查集 正难则反)
题目链接 dalao们怎么都写的线段树合并啊.. dsu跑的好慢. \(Description\) 给定一棵\(n(n\leq 10^5)\)个点的树. 定义\(Tree[L,R]\)表示为了使得\( ...
随机推荐
- 【洛谷5298】[PKUWC2018] Minimax(树形DP+线段树合并)
点此看题面 大致题意: 有一棵树,给出每个叶节点的点权(互不相同),非叶节点\(x\)至多有两个子节点,且其点权有\(p_x\)的概率是子节点点权较大值,有\(1-p_x\)的概率是子节点点权较小值. ...
- Codeforces Round #603 (Div. 2) A. Sweet Problem 水题
A. Sweet Problem the first pile contains only red candies and there are r candies in it, the second ...
- Python连载51-网络编程基础知识
一.网络编程 1.网络.网络协议(一套规则) 2.网络模型: (1)七层模型-七层 物理层(比如网线.锚).数据链路层(比如电压电流).网络层.传输层.会话层.表示层.应用层(我们的活动基本都在这一层 ...
- 四种PHP异步执行的常用方式
本文为大家讲述了php异步调用方法,分享给大家供大家参考,具体内容如下 客户端与服务器端是通过HTTP协议进行连接通讯,客户端发起请求,服务器端接收到请求后执行处理,并返回处理结果. 有时服务器需要执 ...
- path()函数
path()函数具有以下四个参数 route 必须 view 必须 kwargs 可选 name 可选 route route是一个匹配URL的准则(类似正则表达式) 当Django响应一个请求时,它 ...
- SQL Server字符串函数STUFF的使用
前言: 最近有个实践练习是将学生报名数据表student中的[st_id]字段的第二个和第三个字符删除,然后在此位置插入新的字符串“200900”,其实就是替换的意思,生成新的字符串. STUFF 函 ...
- Oracle中的一些基本操作
关于Oracle中的一些基本操作,包括表空间操作,用户操作,表操作 --创建表空间 create tablespace itheima datafile 'I:\oracle\table\itheim ...
- 【CF1172E】Nauuo and ODT(Link-Cut Tree)
[CF1172E]Nauuo and ODT(Link-Cut Tree) 题面 CF 给你一棵树,每个节点有一个颜色. 定义一条路径的权值为路径上不同颜色的数量.求所有有向路径的权值和. 有\(m\ ...
- GO 键盘输入和打印输出
键盘输入和打印输出 一.打印输出 1.1 fmt包 fmt包实现了类似C语言printf和scanf的格式化I/O.格式化verb('verb')源自C语言但更简单. 详见官网fmt的API:http ...
- MySQL EXPLAIN 语句
对于 MySQL 在执行时来说,EXPLAIN 功能上与 DESCRIBE 一样.实际运用中,后者多用来获取表的信息,而前者多用于展示 MySQL 会如何执行 SQL 语句(Obtaining Exe ...