题意
字符串 S 0 = 0 , S 1 = 1 S_0=0,S_1=1 S0=0,S1=1, S i = S l S r ( 0 ≤ l , r < i ) S_i=S_lS_r(0\le l,r <i) Si=SlSr(0≤l,r<i),让你输出 S i [ X i ] S_i[X_i] Si[Xi]
思路
虽然没有做出来,但还是想写个题解。
赛时看到以为是左偏树/可并堆,然后我一直在思考应该怎么调整这个树形结构,发现不是很可行。
赛后看了通过的玩家的代码,发现他们用了倍增,然后我就去思考倍增怎么做。
单链倍增
如果是直接维护倍增,那么时间和空间复杂度都是 O ( n log 2 V ) O(n\log^2V) O(nlog2V),时间或许可行,但是空间一定会炸,大概思路如下:
设倍增数组 n x t [ i ] [ j ] nxt[i][j] nxt[i][j] 表示从点 i i i 往后跳 2 j 2^j 2j 后到达的结点。
我们将 S l S_l Sl 和 S r S_r Sr 连起来以后只需要在 n x t nxt nxt 数组不同的位置新开结点即可,也就是仍在 S l S_l Sl 内的最大的 j j j,这样每次新建 O ( log V ) O(\log V) O(logV) 个结点,一共需要开 1 0 9 10^9 109 级别的 int 和 long long,显然行不通。
树剖+倍增
学校里的大佬告诉我正确的做法用到了树剖,于是我意识到了可以用树剖降掉一个 l o g log log 的空间复杂度。
这个的原理大概就像用树剖写的最近公共祖先(lca)只有 O ( n ) O(n) O(n) 的空间复杂度,但是用倍增写的有 O ( n log n ) O(n\log n) O(nlogn) 的空间复杂度。因为树剖能很好的利用树形结构。
我们将单个字符全部存放在叶子结点,非叶子结点只记录子树大小。关于字符的顺序,就像平衡树那样下标小的放在左边,下标大的放在右边。
当我们令 S i = S l S r S_i=S_lS_r Si=SlSr 的时候,就让结点 l l l 作为结点 i i i 的左儿子,让结点 r r r 作为结点 i i i 的右儿子。
接下来就要连重链,也就是那个儿子的子树大小 s i z [ ] siz[] siz[] 更大,就往哪边连重链。
下一步就需要维护重链上的倍增数组,这个倍增数组是从上往下的, n x t [ i ] [ j ] nxt[i][j] nxt[i][j] 记录从结点 i i i 往下跳 2 j 2^j 2j 步到达的结点, s u m [ i ] [ j ] sum[i][j] sum[i][j] 记录从结点 i i i 开始的 2 j 2^j 2j 个重链上的结点的非重链左儿子(如果有)的子树大小之和。注意到,重链的长度最多为 O ( n ) O(n) O(n),因此第二维只用开到 20 20 20 即可。
最后就是查询 S i [ X i ] S_i[X_i] Si[Xi],总体思路就是先从重链往下跳,然后走轻儿子,然后再从重链往下跳,以此反复,直到到达结点 0 0 0 或者结点 1 1 1