让我们重新看一看

· · 题解

这篇题解中,我将从线段树的角度重新讲解,同时提出一个扩展。

我们可以用权值线段树维护最长连续段:线段树每个节点维护 L,R,M,S 分别表示这个区间的左边连续段长度、右边连续段长度、最大连续段长度、区间长度,这样就可以实现区间合并。

这样建的线段树如图:

其中橙色线段代表左儿子,蓝色线段代表右儿子,下同。

为了方便后文叙述,“层”从下往上、从 0 开始编号,层的最大编号为 k,每一层的节点从 0 开始编号。

考虑一个异或 x 的操作,比如 x=5,如果固定底层的节点不变,则线段树会长这个样子:

可以发现,实际上是把第 1 层和第 3 层的节点交换左右儿子。

实际上,对于异或 x 的操作,若 x 二进制第 i 位(从低到高,从 0 开始编号)为 1,则将线段树第 i+1 层的左右儿子交换。

对于所有的异或 x 的操作,线段树的节点会有很多重合部分,把所有的操作的线段树建出来并合并,则会是这个样子:

有几个性质:

对于本题,第 k 层节点的 M 值便是答案。

实际上,由于是线段树,它可以维护区间合并。也就是说,下标异或的背景下,可以 O(\log n) 求一般的可以区间合并的区间查询,但不支持快速修改(如图,一个底层节点涉及的节点是 O(n) 的)。

比如有以下问题:

#include <bits/stdc++.h>

using namespace std;

const int M = 21, N = 1 << 19 | 5;

int m = 19, n = 1 << m, q, t, x, y;
struct node{
    int l, r, m, p;
}s[2][N];

void pushup(node &u, node &l, node &r)
{
    u.l = l.p ? l.l + r.l : l.l;
    u.r = r.p ? r.r + l.r : r.r;
    u.m = max(l.r + r.l, max(l.m, r.m));
    u.p = l.p & r.p;
}

int main()
{
    scanf("%d%d", &q, &t);
    while(q -- ) scanf("%d", &x), s[0][x] = {1, 1, 1, 1};
    for(int i = 1, x = 1, y = 0; i <= m; i ++ , x ^= 1, y ^= 1)
        for(int j = 0; j < n; j ++ )
            pushup(s[x][j], s[y][j], s[y][j ^ (1 << i - 1)]);
    while(t -- ) scanf("%d", &x), y ^= x, printf("%d\n", s[1][y].m);
    return 0;
}