很神奇的题目。
首先你考虑我们如何把那个异或转化掉。直接异或显然是不好做的。
异或考虑拆位。考虑我们如何比较两个二进制数的大小?我们从高到低考虑,如果一个是 而另一个是 那么前一个就小于后一个了。
我们忽略 的限制,将所有的 都插入到一颗 trie 树上。考虑此时插入 。我们用 表示 二进制从高到低的第 位,第 位是代表 的那位。假设插入到第 位,假设 ,那么我们反转 ,那么之后 之后的位无论怎么填都会小于 了。这就对应了 trie 树上的一颗子树。
因此,我们成功将一个异或的限制转化成了 个子树,可以用 dfn 编号的方法转化成不相交的 个区间。然后我们发现对于每个岛屿也可以用类似的方法转化成 个区间,表示这个岛屿对询问的 的限制。因此实际上我们是要对这个进行计数: 其中 分别表示转化之后的若干个区间。
我们发现同一个询问或岛屿的所有区间是不交的,因此我们直接拆成 次操作也不会算重。对 和 那一维进行扫描线,我们发现这是一个动态二位数点问题。直接上 CDQ 分治!复杂度 。似乎有点爆炸。
你发现第三个限制似乎不是很美观。实际上,我们只需要考虑 和 中较小的那个即可。因此我们直接分 和 两种情况,我们对 这个维度扫描线,对另外两个维度进行二位数点即可。但是这个复杂度没有更优啊?
我们不妨换一个角度。考虑树套树,我们直接在 tire 树上每个结点开一颗线段树,插入岛屿的时候就直接给经过节点的对应位置加一即可,查询就是区间查询。
此时你发现这个算法复杂度就变成 了。
但是树套树太难写了怎么办?我们发现,有 次查询,每次查询是 的。这里造成了复杂度的瓶颈。实际上,trie 树相当于一颗值域线段树,而我们每次要查询一个节点的和,所以我们其实可以 直接查询。换个角度想,其实就是暴力维护树上前缀和,但是树高只有 。在 CDQ 分治的时候,插入时对对应节点的 加一,查询的时候累计上对应节点的和即可。由于插入的节点显然和询问次数有关,所以直接暴力删掉 trie 的所有节点复杂度也没有问题。这实际上对应了前面问题的静态版本,我们通过 CDQ 花费一个 的复杂度将问题转化成了一个静态问题。
参考代码在这里。