P11160 機械生命体 解题报告
P11160 機械生命体 解题报告
1. 题意化简
首先,我们用更直白的话来描述这道题的目标。我们需要维护一个数字集合,并实现四种功能:
操作1/2
:向集合里加一个数或删一个数。这是基本操作。操作4
:给定一个数x
,在集合中找一个数y
,让x
和y
的二进制表示从右往左(从低位到高位)有尽可能多的连续相同位。我们要输出这个“最长相同后缀”的长度所代表的值。- 例如,
x=10 (1010b)
,y=2 (0010b)
。它们从右往左看,...0010
和...1010
,低三位010
是相同的,直到第3位(从0开始数)才不同。所以lowbit(x XOR y)
的结果是2^3 = 8
。我们的目标就是最大化这个值。
- 例如,
操作3
:给定x, k, v
。找到集合里所有满足“低k
位和x
完全相同”的数y
,然后把它们都加上v
。
2. 核心工具:01-Trie(字典树)
处理位运算问题,尤其是涉及前后缀匹配的,01-Trie 是一个非常强大的工具。
- 建树方式:这道题的关键在于低位。无论是操作3还是操作4,条件都和“低位匹配”有关。因此,我们建立一棵从低位到高位的01-Trie。树的第
d
层节点,代表的是数字的第d
位。 - 节点信息:每个节点需要记录一个
size
,表示有多少个集合中的数字的二进制路径经过了此节点。
3. 操作实现
操作 1 & 2:插入与删除
这是最基础的Trie操作。我们从根节点(代表第0位)开始,根据要插入/删除的数 x
的每一位,选择走左儿子(0)还是右儿子(1),并更新路径上所有节点的 size
值。
操作 4:查询最大lowbit
这个操作在我们的Trie上变得非常直观。我们的目标是找到一个与 x
低位匹配最长的 y
。
- 贪心策略:我们从根节点(第0位)出发,严格按照
x
的二进制位往下走。比如x
的第d
位是0,我们就尝试往左子树走。 - 寻找分叉点:只要路径上对应子树的
size > 0
,就说明集合中存在至少一个数,在这一位上和x
是相同的。我们就继续往下走。 - 找到答案:当我们走到第
d
层,发现x
对应的下一个子节点size
为0时,这意味着什么?这意味着所有与x
低d-1
位都匹配的数,在第d
位上都与x
不同。因此,d
就是x
和集合中某些y
第一个不同的位。所以最大lowbit
值就是2^d
。
简单来说:顺着 x
的位路径在Trie上走,第一个走不通(size=0
)的深度 d
,就是答案的指数,结果为 2^d
。
操作 3:条件批量加 v
(难点)
这是本题最复杂的部分。我们需要对所有低 k
位与 x
相同的数 y
,执行 y = y + v
。
难点分析:加法操作会产生进位,这会彻底改变一个数的二进制结构,直接在Trie上修改非常困难。例如 7 (0111b) + 1 = 8 (1000b)
,低位全变了。
解决方案:懒标记 (Lazy Tag)
我们不在访问时立刻执行加法,而是在节点上打一个“待办事项”标记。
-
定位子树:首先,所有低
k
位与x
相同的数,在我们的Trie上都聚集在同一个子树里。我们可以沿着x
的低k
位路径走k
步,找到这个子树的根节点。 -
子树整体加
v
:现在问题简化为:如何让一个Trie子树里的所有数都加上v
?- 引入
tag
:我们在每个节点上增加一个tag
值。node.tag = T
的意思是:“凡是路径经过此节点的数,最终的值都要额外加上T
”。 - 下放标记
pushdown
:当我们真正需要访问一个节点的子节点时,我们才处理它的tag
。- 处理奇数部分
+1
:如果tag
是奇数,我们先处理+1
。一个数n
加 1:如果n
的最低位是0,就变成1;如果最低位是1,就变成0并向上进位1。在Trie上,这表现为交换左右子节点,并且给那个原本代表奇数的子树(现在是左子树了)的tag
再加1(处理进位)。 - 处理偶数部分
+2m
:tag
剩下的偶数部分2m
,下放到子节点就变成了+m
。这是因为(数/2) + m
正好对应下一层的加法。所以,我们把tag/2
的值分别加到左右子节点的tag
上。
- 处理奇数部分
- 引入
-
最终实现
solve
函数:题解的代码非常巧妙,它把y+v
这个操作分为两步:- A. 处理低
k
位变化:y
的低k
位本来和x
一样,加上v
后,它的新低k
位会变成(x_low_k + v)_low_k
。这在Trie上表现为整个子树要从旧路径移动到新路径。solve
函数通过递归,将子树从原路径上“剪切”下来,“粘贴”到新路径上。 - B. 处理高位进位:低
k
位的加法完成后,可能会产生一个进位,值为(x_low_k + v) >> k
。这个进位会影响到第k
位及更高的位。我们将这个进位值作为一个懒标记tag
,打在刚刚移动完成的子树的根节点上。 - 合并
merge
:由于移动后的新路径上可能已经有别的数存在,我们需要将移过来的子树与新路径上已有的节点进行合并(类似于线段树合并)。
- A. 处理低
总结
本题的解法可以概括为以下几个关键点:
- 低位到高位Trie:完美契合题目中对低位匹配的要求。
- 查询的贪心思想:沿着
x
的路径走,直到路径中断,即可找到答案。 - 懒标记处理加法:用
tag
记录待加的值,通过精巧的pushdown
逻辑(交换子节点处理+1
,下放tag/2
处理+2m
)来模拟二进制加法。 - 移动子树+懒标记进位:将复杂的操作3分解为“改变低位路径”和“高位进位打标记”两步,使得问题得以解决。