dsu on tree
天哪!为什么这么多人讨厌我!一定是我们被人做局了,动了别人的面包、冰淇淋鸡尾酒、甜甜圈、樱桃味杯糕、草莓慕斯、糖霜苹果、巧克力奶……啊什么,吃掉这些的都是我吗?欸?
咳咳,天依是宇宙级大厨,所以没关系的!来吧来吧,就算是黄金和珍珠,天依也能大手一挥,做成最好吃的东西哦!
什么头号吃货……别把天依当成那样肤浅的存在!天依的口味可是很刁钻的,如果不是最最上等的美食,天依才不要吃呢。
什么是最上等的美食?嗯,刺激味蕾和视觉都还不够,平凡外表的食物也会有惊人的甜美,原生的食材也会有清新的口感。天依觉得,是爱吧。倾注爱意的美食,每一次烹饪,如果用心,那么不管是普通的佐料还是上等的食材,天依都很喜欢。因此……所谓的上下等,也只是是否用心而已。天依不在乎厨艺,天依只在乎心。
那么,端上来吧!天依满怀爱意为你制作的,只属于你和我的甜点!……什么,天钿?你把天钿吃了吗?!快吐出来!!
dsu on tree
人类智慧算法,核心思想就是在树上操作的时候,需要对每个点维护一个通常 \(\ge O(n)\) 的东西,然后通过从最大的子树继承、其它子树暴力的方式来节省复杂度。
有很强的拓展性,同时也不容易想到。
天依宝宝可爱!
一个很经典的问题
思维难度:\(\color{#F39C11} 橙\) *900
给定一棵有根树 \(T\),节点 \(u\) 有颜色 \(c_u\),对于每棵子树,求出子树内节点的颜色种类数。
显然对每个节点分别 dfs 统计一遍是不行的,因为如果设 \(s_u\) 为子树 \(u\) 的颜色个数,那么:
但是可以发现,有很多东西是被重复统计了的,所以考虑父亲从儿子处转移,这个可以看做 set 的合并操作,所以显然从子树大小最大的 \(v\) 转移,再暴力统计轻儿子,这样最优。
看起来复杂度似乎很玄学,但实际上就是很玄学,设 \(wson(u)\) 为 \(u\) 的重儿子(树剖中的定义):
Proof. 和证明树剖复杂度的方法类似,考虑结点 \(u\) 对它的祖先们的贡献。如果 \(u\) 不在其祖先 \(x\) 所在的重链中,那么 \(u\) 就会对 \(x\) 产生贡献,因为树剖中点 \(u\) 最多往上有 \(\log n\) 条重链,所以点 \(u\) 产生的贡献是 \(O(\log n)\) 的,故总复杂度 \(O(n \log n)\)。
所以回到这个题,可以直接对每个点维护一个 cnt(bitset)表示每个颜色是否出现,然后对于一个节点 \(u\),保留其重儿子的 cnt 作为 \(u\) 的 cnt,然后暴力遍历所有轻儿子及其子树,更新 cnt 即可。
天依宝宝可爱!
CF600E
思维难度:\(\color{#F39C11} 橙\) *1000
显然把上面那个题改一改就可以了。
然而 CF 炸了。

天依宝宝可爱!
洛谷 P4149
思维难度:\(\color{#FFC116} 黄\) *1500
显然在 dfs 过程中拿 map 维护每个边权和对应的最小长度就行了,但是偏移量的维护挺麻烦的,甚至需要维护两个偏移量。
天依宝宝可爱!
CF1709E
思维难度:\(\color{#FFC116} 黄\) *1600
dfs 过程中维护子树 \(u\) 中每个节点到 \(u\) 的异或和(打 tag),过程中可以判断是否存在不合法路径。
贪心地,如果 \(u\) 子树中没有单独的不合路径,且存在以 \(u\) 为两端点 LCA 的不合法路径,那么 \(u\) 一定删除,这样一定是最优的。推广一下,自底向上,如果遇到满足「存在以 \(u\) 为两端点 LCA 的不合法路径」的点 \(u\) 就直接删除,同时删除整个子树,因为它们已经不会产生任何贡献了。这样处理即可。
注意:
- 要统计两类路径:一类是 LCA 不在端点上的,另一类(容易忘记的)是 LCA 在端点上的。第二类还要注意由重儿子连向父亲的边与轻儿子是否不同。
- 不要同时合并两个 set 和检查答案,原因显然,要先检查答案再合并。
- 在暴力 dfs 轻子树的时候,要注意对于每个节点都判一下是否被删除。
天依宝宝可爱!
[补]UOJ 284
思维难度:\(\color{#3498DB} 蓝\) *2200
思维量好大,脑子不够用了。
考虑只有一组询问 \((s,t)\) 的时候应该怎么做。
首先直接走 \(s \leadsto t\) 上的边不一定是最优的,因为这样的实际消耗时间可能比 \(s \leadsto t\) 上的最大 \(w_i\) 大很多。
所以要考虑如何才能用尽量少的时间使得死亡次数达到路径上的 \(w_{\max}\)。可以考虑从 \(s\) 出发到一些附近的点去送死,这样累加死亡次数,直到达到 \(w_{\max}\) 再直接走 \(s \leadsto t\)。
那么问题就转化成了求从一个点 \(s\) 出发送死 \(w_{\max}\) 次的最小时间,且只能往 \(s\) 的子树里走。
考虑对于子树中每个点 \(i\) 记录二元组 \((d,w)\),\(d\) 表示 \(i\) 到 \(s\) 的距离,\(w\) 就是 \(w_i\)。那么设当前死亡次数为 \(x\),我们要选的点一定是满足 \(w_i > x\) 且 \(d_i\) 最小的点,显然可以用单调栈维护,先把这一堆点按照 \(d_i\) 排序,再在单调栈里维护 \(w_i\) 的前缀最大值即可。
在计算的时候,显然可以在单调栈里面二分,二分一个临界点,这个点前面的点都跑满(即一直送死直到 \(x = w_i\)),这个点跑到一半当 \(x = w_{\max}\) 的时候就停止。
然后尝试推广到 \(q\) 组询问,显然对于任何一个询问,最重要的就是子树 \(s\) 的这个单调栈。
那么问题就转化成了如何启发式合并单调栈了。
我们现在有一个重儿子的单调栈 \(A\) 和一个由若干个轻儿子组成的单调栈 \(B\)。注意到直接往 \(A\) 的栈顶插 \(B\) 的元素是不行的,因为这样就保证不了 \(d_i\) 递增了。
为了保证 \(d_i\) 递增,一个暴力的做法就是直接把 \(A\) 中不超过 \(B\) 中 \(d_{\max}\) 的元素都拿出来,重新做成一个单调栈再直接拼到 \(A\) 的后面。
这样看起来是不对的,但是真的不对吗?
这时候就需要一点注意力了,显然 \(d_{\max}\) 是 \(\le \sum siz_v\) 的(\(v\) 为 \(u\) 的轻儿子),又因为单调栈中同一个 \(d\) 中对应的值最多只有一个,所以 \(A\) 中拿出来的部分是和 \(B\) 的大小同量级的!
这样,大体思路就完成了,但是还有一大堆细节需要处理,巨难写。比如说为了满足二分的需要,要维护单调栈的加权前缀和;在合并的时候,可能会出现 \(A\) 中元素不是前缀最大值的情况,这时候复杂度又应该如何保证,前缀和又应该如何维护……
目前还没有的 submission
天依宝宝可爱!

浙公网安备 33010602011771号