植树门
https://www.luogu.org/problemnew/show/P3401
题解
今天是植树节,所以我就植了一棵洛谷树。
这题是某年GDKOI魔卡少女的强化版,将区间操作换到了树上,我们可以采用LCT或树链剖分来维护。
不知道为什么,我就是想写树链剖分,大神hzh弃掉的树链剖分(现在终于写出来了)。
这题的难点在于两处:一是区间上如何维护子区间的异或和,二是转到树上应该怎么剖。
先考虑①:由于异或满足交换、结合律,我们可以用线段树合并左右两个区间的信息。而异或每一位具有独立性,我们可以开10棵线段树来分别搞。
据其性质,凡是线段树维护一段子区间什么的,往往都是维护左边、右边和全部。题目要求我们对每个子区间异或值求和,我们当然换而考虑每一个值的贡献(这样想很重要)。在这题里,我们要维护的贡献就是异或值为1的子区间的数量。这就变成了线段树上的计数问题。废话少说,根据套路,维护下面6个值:
Val, Lnum0, Lnum1, Rnum0, Rnum1, Tnum
分别代表区间的异或值,从左端点开始的异或值为0的子区间数量,…(依此类推)…,区间中异或值为1的子区间数量。怎样维护参见下面代码中的Up()函数。这里不算太难。
然后算答案时就将每一位的权乘上值为1的子区间数量,注意这里要用long long。
再考虑②:现在区间转移到了路径上,我们剖出重链、轻边,然后按照dfs序用线段树维护。这里并不像树上求和一样,可以每一部分分开算,从x->lca的路径要连续的维护(从上往下),从y->lca的路径同理。由于维护父边,所以lca的权去掉,然后最关键的一步:将x得到的答案反过来(R, L),再和y接起来。由于子区间必须连续,所以这样做很好理解。
大体的思路就是这么多,具体写法参见Code。
这里还有几点我曾犯下的错误,放在这里:
①一开始没考虑路径方向
②树剖前的初值没有赋6个0
③没开long long
④想着少维护一点,没有维护左右开始的0区间的个数,而是想着用长度算出来,但线段树每次下去后长度又不好知道(当作胡扯),所以我对线段树的理解还不够深入,还是写得规矩点好
⑤树剖忘加siz
⑥(1 << <script type="math/tex" id="MathJax-Element-7"><<</script>i)&x并不等于0或1!要错多少遍才能不这么愚蠢!
⑦树剖一开始想得很复杂,和lca的分类讨论一堆
⑧…
经过我一个下午和一节晚修的奋斗挣扎,总算调出来了,写了一个跑得贼慢但是勉强能过的程序。。。
代码
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <cstring>
#