ULR #2 F. Picks loves segment tree IX

简要题意

给定长度为 \(n\) 的一个操作序列,每个操作形如:

  1. and \(x\)
  2. or \(x\)
  3. xor \(x\)
  4. add \(1\)

其中 \(x\) 对于每个操作可能不同。

若干组询问(强制在线),每组询问形如:给定区间 \([l, r]\),非负整数 \(v\),问如果以 \(v\) 为初始值依次进行 \([l, r]\) 内的操作,最终结果的第 \(k\) 位是 \(0\) 还是 \(1\)

数据范围:\(1 \le n \le 10^5, 1 \le q \le 6\times10^5, 0 \le v, x < 2^{32}\)

值得考量的部分分:

  1. 不强制在线
  2. \(n \le 4\times10^4\)
  3. 不存在 and/or

题解

不强制在线

考虑一个经典的反序 01-Trie,从低位到高位存数,那么位运算不受影响,add \(1\) 操作表现为从根节点开始交换左右儿子,并进入左子树递归,时间复杂度为 \(\Theta(\log V)\)。将操作放到它对应的区间左端点,扫描操作序列,在左端点处将 \(v\) 插入 01-Trie,同时记录在 Trie 上的标号,在区间右端点处通过不断跳父亲得到 \(v\) 的当前值。

\(n \le 4\times10^4\)

考虑可持久化,将 01-Trie 放到线段树上,然后对于每个节点只维护有意义节点。由于操作中只有 add \(1\) 会带来 \(\Theta(\log V)\) 级别的新增节点,所以时空都是双 \(\log\) 可以通过。

不存在 and/or

考虑前一种做法中为什么我们需要一棵线段树:这是因为操作不具有可减性,也就是有一些操作没有逆。现在我们采用前缀和做法。采用可持久化 01-Trie 维护正序操作。接下来考虑如何求逆,容易得出只需要在节点上维护原本的值(范围)和 tag,然后在树上查询某个节点对应的原数值即可。时空单 \(\log\)

正解

考虑 add \(1\) 操作的特殊性:可能发生进位。考虑消除这一点影响。发现对于一次 add 如果这次 add 结束以后的 \(v\) 末尾有至少 \(k+1\)\(0\)(也就是 \(0 \sim k\) 位为 \(0\)),那么第 \(k\) 位在这次操作中一定受到了一次 xor,否则对这一位是没有影响的。

然后考虑询问中只需要求特定的一个 \(k\),那么我们根本不关心更高的位上的数值到底是什么;同时,没有影响到第 \(k\) 位的 add \(1\) 也不那么重要。接下来考虑 \([l, r]\) 内所有导致第 \(k\) 位变化的 add \(1\),分析一下。发现在这些操作以后低 \(k\) 位都是 \(0\),情况是唯一的。所以非常好做。接下来考虑找出所有这些操作。记 \(f_{k, i}\) 表示低 \(k\) 位全是 \(0\) 的一个数从 \(i\) 操作开始做需要做多久才会又变成低 \(k\) 位全是 \(0\),想到去求这个东西很自然,然而会发现这个不是那么好求的!因为其实在 \([i, f_{k, i}]\) 这段里面还会遇到一些 add \(1\),这就非常不好。

所以就很容易猜到 \(f_{k, i}\) 的求值是依赖于更小的 \(k\) 的。那么可以想到找一下 \(f_{k-1, i}\) 这个东西,发现中间这一段的操作既然都没能恢复第 \(k-1\) 位,那么对于第 \(k\) 位的贡献肯定也是朴素的区间位运算,非常容易算出这个位置上第 \(k\) 位是不是 \(1\),如果是那么就好了,否则就得接着跳。这样其实已经差不多了,但是还是有一点小问题——不能真的去不停地跳 \(f_{k-1, i}\)。这个事情是好办的,只需要记 \(g_{k, i}\) 表示从第 \(i\) 个操作开始,末尾有 \(k+1\)\(0\),第 \(k+1\) 位则一定为 \(1\),什么时候会让末尾的 \(0\) 个数增加 \(1\)。那么这个求出 \(f_{k, i}\) 以后只要倒着递推一下就可以,然后利用 \(g_{k-1, i}\) 很容易算 \(f_{k, i}\)

现在我们有了 \(f\) 数组,怎么回答询问?从 \(l\) 开始不停地跳 \(f\) 数组直到马上跳出区间即可。这样复杂度好像不对?其实把跳跃的终点看成树上父亲,每个 \(k\)\(f\) 数组都是森林,只需要用个树链剖分就可以了。注意不能倍增是因为双 \(\log\) 空间会炸。实际上这种每个点有唯一出度的结构用树剖替代倍增都可以说比较优。

当然,还有一个问题是第一段,因为询问是给定某个 \(v\) 的,也就是第一段并不满足起始值是后 \(k\) 位全 \(0\)。这就很难受。没关系,我们可以利用 \(g\) 数组,像在计算 \(f\) 时那样不断增加 \(v\) 末尾的 \(0\) 数量。显然这么做次数不会超过 \(\Theta(\log V)\) 次,所以复杂度很对。

代码

咕咕咕

posted @ 2023-07-03 21:49  kyEEcccccc  阅读(126)  评论(0)    收藏  举报