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,一类树上离线问题的做法的更多相关文章

  1. 总结-DSU ON TREE(树上启发式合并)

    考试遇到一道题: 有一棵n个点的有根树,每个点有一个颜色,每次询问给定一个点\(u\)和一个数\(k\),询问\(u\)子是多少个不同颜色节点的\(k\)级祖先.n<=500000. 显然对每一 ...

  2. dsu on tree:关于一类无修改询问子树可合并问题

    dsu on tree:关于一类无修改询问子树可合并问题 开始学长讲课的时候听懂了但是后来忘掉了....最近又重新学了一遍 所谓\(dsu\ on\ tree\)就是处理本文标题:无修改询问子树可合并 ...

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

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

  4. DSU on Tree浅谈

    DSU on tree 在之前的一次比赛中,学长向我们讲了了这样一个神奇的思想:DSU on tree(树上启发式合并),看上去就非常厉害--但实际上是非常暴力的一种做法;不过暴力只是看上去暴力,它在 ...

  5. dsu on tree详解

    这个算法还是挺人性化的,没有什么难度 就是可能看起来有点晕什么的. 大体 思想是 利用重链刨分来优化子树内部的查询. 考虑一个问题要对每个子树都要询问一次.我们暴力显然是\(n^2\)的. 考虑一下优 ...

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

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

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

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

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

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

  9. 树上统计treecnt(dsu on tree 并查集 正难则反)

    题目链接 dalao们怎么都写的线段树合并啊.. dsu跑的好慢. \(Description\) 给定一棵\(n(n\leq 10^5)\)个点的树. 定义\(Tree[L,R]\)表示为了使得\( ...

随机推荐

  1. efk

    准备三台虚拟机 都安装 zookeeper kafka kafka01   192.168.202.131   +elasticsearch + kibana    kafka02 192.168.2 ...

  2. MySQL select from where multiple conditions

    Maybe one of the most used MySQL commands is SELECT, that is the way to stract the information from ...

  3. golang数据结构之插入排序

    //InsertSort 插入排序 func InsertSort(arr *[]int) { ; i < len(arr); i++ { insertVal := (*arr)[i] inse ...

  4. linux--解决anaconda升级pip问题

    Anaconda指的是一个开源的Python发行版本,其包含了conda.Python等180多个科学包及其依赖项. 在用pip install命令安装东西时,有时会提示如下错误:升级pip You ...

  5. 实例属性和方法的动态处理(__getattr__)

    正常情况下,当调用类的方法或属性时,如果不存在,就会报错 要避免这个错误,除了可以加上那个要调用但不存在的属性外,Python还有另一个机制,那就是写一个__getattr__()方法,动态返回一个属 ...

  6. 解决ie下vue列表数据不能即时刷新的问题

    项目上要兼容IE浏览器(客户要求),发现之前在谷歌浏览器下,操作(增删改查)列表后列表能即时刷新(双向绑定),IE下却不行. 自己调试一下发现,在IE11下,如果GET请求请求相同的URL,默认会使用 ...

  7. ASP.NET(1)

    1.IIS安装问题,先装VS再装IIS,处理程序映射有问题,使用VS自带的控制台输入命令,注册路径 2.开发模式,一般处理程序,使用IO操作读取html文件,使前后端分离 3.post请求和get请求 ...

  8. Unity Pixel 人物设计(1)

    草稿: 目前进度:

  9. Docker关于镜像、容器的基本命令

    镜像 1.获取镜像 docker pull 服务器:端口/仓库名称:镜像 ➜ ~ docker pull python Using default tag: latest 2.查看镜像信息 列出本机所 ...

  10. [日常] 解决github速度特别慢

    执行下面这俩命令,找到对应的IP,增加host就能解决 nslookup github.global.ssl.fastly.Netnslookup github.com root@tao-PC:/va ...