P8353 题解
「另,这题和树剖一分钱关系都没有。」——出题人。
注意:本题解做法使用 树剖。且该做法无法在大部分 OJ 上评测。但理论上考试本地评测时可通过。且允许在考场使用。
Upd. 5/27: 感谢 @liuzhangfeiabc,目前已找到一种可以在洛谷和 LOJ 通过的方式。
Subtask \bf 1
暴力即可。用一个数组存点权,另一个数组存父亲,每次修改时暴力跳父亲修改。
Subtask \bf 2
该点满足特殊性质:点
可以证得这样生成的树以
由于树高期望
Subtask \bf 3 \sim 16
该点满足特殊性质:树退化为链。
链上的区间加区间和可以用多种数据结构维护。但是此处由于内存卡的较紧,仅能开下两个长度为
Subtask \bm{{n \le 3 \times 10^6}}
树上问题的一种维护方式就是树剖。轻重链剖分的大众写法需要开 fa、sz、dep、hson、top、dfn 六个数组。本题中显然无法开下。考虑将每个数组用于多种用途。我们发现,sz、hson、top 三个数组可共用一个数组。且由于题目保证 for 加上一个 bitset 代替。同时由于 dep 数组是无必要存在的。跳链时只需要比较链顶的 dfn 即可。
树剖完成后同样用分块维护。空间是
Subtask \bm{{n \le 4 \times 10^6}}
如果你做过 MCOI R4A,你可能会知道一个时间换空间的方法。可以参考 该题解 中的「优化 A」部分。
我们发现 Subtask fa、top、dfn 三个数组值域全是
分块块长为
Subtask \bm{{n \le 6 \times 10^6}}
树剖的三个辅助数组还是太多了,能改成两个吗?
当然可以。我们发现,树剖在跳链的过程中,对于一个是某个节点的重儿子的节点只会从它跳到链顶,否则只会从它跳到它的父亲。考虑把 fa 和 top 存到一起,使用 bitset 存储每个点是否是一条链的链顶。然后我们就可以少开一个数组而通过该 Subtask 了!
分块块长为
Subtask \bm{{n \le 7 \times 10^6}}
把
观察题目中的这个强制在线形式,发现所有输入会异或答案对
可以发现此时内存可以开下了。但是我们只有答案的低
注意此时我们可以得到所有解密后的询问,并以
问题来了,fa 数组已经不存在了,如何还原树的形态呢?
如果是在 OJ 上测评,且 OJ 允许操作文件指针,则可以使用 fseek(stdin,0,SEEK_SET); 让文件指针回到文件开头重新读取。如果是在本地,使用文件输入输出,我们可以直接 fclose 把文件关了后重新 freopen 打开再读一遍。
做完第一遍后,记录解密后的数据,并且把内存池整个清空,重新分配内存,重新再做第二遍。
该做法在 Arch Linux 上的 LemonLime 0.3.3 测试,编译器为 G++ 11.3.0,编译选项 -std=c++11 -O2,评测机 CPU 为 Intel i5-8259U。经测试程序运行内存为
代码参考(包含最终解法和题解中说明的大部分 Subtask 的暴力解法)。