codeforces 2045J Xorderable Array
因为这个 \(a\) 数组是给定的,所以就不太能从 \(a\) 数组入手了。
于是考虑什么情况下的 \((p, q)\) 是合法的。
首先因为条件既涉及 \(a_i\oplus p\) 又涉及 \(a_i\oplus q\),这显然是不想看到的。
因为 \(p\oplus (p\oplus q) = q\),所以可以通过适当的转化使得一边不带 \(\oplus\) 而一边带 \(\oplus\),这形式上显然是更优美的。
于是令 \(b_i = a_i\oplus p, t = p \oplus q\),那么相当于是要满足存在 \(b_{1\sim n}\) 的排列方式使得 \(\forall 1\le i < j \le n, b_i < b_j\oplus t, b_i\oplus t < b_j\)。
接下来的问题就是考虑如何判断存在 \(b_{1\sim n}\) 的排列使得满足条件,对此可以考虑构造出这个排列。
考虑到的是这里满足的条件是 \(<\) 关系,且涉及操作是 \(\oplus\)。
于是这启发按照二进制从高位到低位去构造。
令还没有被区分开大小的一些数在同一个等价类中,且对于不同的等价类相互大小关系已经确定。
那么就只需要考虑不断确定等价类内大小关系并继续划分即可。
于是分讨 \(t\) 在这一位的值:
- \(t\) 这一位为 \(0\)。
那能够发现的是可以把等价类内这一位为 \(0\) 的放在左边,为 \(1\) 的放在右边。
那么此时这个等价类就被划分成了左右两个等价类,因为 \(0, 1\) 相比较大小关系已经确定了。 - \(t\) 这一位为 \(1\)。
那能够发现若等价类内这一位为 \(0 / 1\) 的数的个数 \(> 1\) 则一定无解,因为 \(x\le x \oplus 1, x \oplus 1 \le x\) 一定不能同时满足。
否则此时因为 \(0\oplus 1 = 1, 1\oplus 1 = 0\),所以无法比较出这一位为 \(0\) 和为 \(1\) 的大小关系,于是当前等价类保持不变。
只要一直操作到底还不会出现无解的情况就能构造出合法解,因为此时等价类互相大小关系确定了可以直接放。
且等价类内的元素相互比较出来一定是 \(=\) 的关系,也可以随意排布。
同时能够发现一个有趣的事实。
实际上对于 \(b_i\) 只关心这一位是否相同然后去划分等价类判无解,那就说明 \(b_i = a_i\oplus p\) 的这个 \(p\) 实际上是没有意义的。
那么也就说明其实能否划分出来只与 \(t = p\oplus q\) 有关而不与 \(p, q\) 的值有关。
且同时根据上述的思考能够发现直接用 \(a\) 去进行这个操作得到的是否有解是和 \(b\) 是一样的。
那么就可以带入 \(a\) 去模拟这个划分等价类的过程,就可以求出来对应的 \(t\) 的一些范围关系。
但是这样常数太大了,考虑多发现一些性质。
首先,若 \(t\) 这一位为 \(1\),则每个等价类的元素个数至多为 \(2\) 个,且如果为 \(2\) 个则这一位一定一个为 \(0\) 一个为 \(1\)。
因为此时不能出现两个相同的值。
于是可以知道的是,从高到低位的一个前缀 \(t\) 都需要为 \(0\),直到最大的等价类元素个数 \(\le 2\) 才可能用到 \(1\)。
其次,每个等价类一定对应的是 \(a\) 排序后的一段区间。
根据上面的分析,因为一开始的高位 \(t\) 都为 \(0\),那么就相当于是直接用这些高位进行比较来划分等价类。
所以当还没用到 \(1\) 时每个等价类一定对应的是一段区间。
而当用到 \(1\) 时,每个区间长度至多为 \(2\),那么如果后面的 \(0\) 区分开了长度分别为 \(1, 1\) 也是区间。
于是就可以在对 \(a\) 排序后用区间来表示等价类了。
首先可以一直去划分直到最大等价类元素个数 \(\le 2\) 时,记录下元素个数为 \(2\) 的等价类 \(\{a_i, a_{i + 1}\}\) 的 \(x = a_i\oplus a_{i + 1}\),放入集合 \(C\) 中。
考虑继续往低位走考虑 \(t\) 每一位能为什么:
- \(\forall x\in C\),\(x\) 这一位为 \(1\)。
那么如果 \(t\) 这一位为 \(0\),就可以直接区分出所有数,不管 \(t\) 更低的位为什么都合法。
也可以让 \(t\) 这一位为 \(1\),\(C\) 集合不变。 - \(\exists x\in C\),\(x\) 这一位为 \(0\)。
那么 \(t\) 这一位就只能为 \(0\) 了。
同时对于 \(C\) 只保留 \(x\in C\),\(x\) 这一位为 \(0\) 的数。
通过上面的分讨能够知道,最后合法的 \(t\) 放在 01trie 上形如一条路径,且路径上的部分节点的非路径上儿子的子树是满的。
或许描述着有点抽象了,可以根据上面的分讨画一画 01trie 就知道了。
那么这就可以直接用 01trie 维护了,即每次加入 \(b_i\) 时对应在 01trie 上走的时候顺带统计 \(t = b_i \oplus b_j(j < i)\) 合法的个数。
只需要维护子树的 \(\operatorname{size}\) 就可以求解,这个可以在插入 \(b_i\) 时顺带更新。
时间复杂度 \(\mathcal{O}(n\log n + (n + m)\log V)\)。
顺带一提,这题有个很优美的结论是在对 \(a\) 排序后,合法的 \(t\) 满足 \(t\le \operatorname{mn} = \min\limits_{i = 1}^{n - 1} a_i\oplus a_{i + 1}\)。
其实这个结论根据上析分析过程是很好看出的,可以模拟过程发现其他相邻数对要么和 \(\operatorname{mn}\) 一起被区分出要么比 \(\operatorname{mn}\) 更早区分出,所以只需要关心区分出 \(\operatorname{mn}\) 即可。
这样可以做到 \(\mathcal{O}(n\log n + m\log V)\)。
给出的代码不是用的结论而是通过模拟得出 \(t\) 的合法范围。
#include<bits/stdc++.h> using ll = long long; constexpr int maxn = 2e5 + 10, maxm = maxn * 30; int n, m, k, k_; int a[maxn], b[maxn], c[maxn], c_[maxn]; bool c0[30]; int siz[maxm], son[maxm][2], tn; int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) scanf("%d", &a[i]); for (int i = 1; i <= m; i++) scanf("%d", &b[i]); std::sort(a + 1, a + n + 1); int w; for (w = 29; ~ w; w--) { int msk = ~ ((1 << w + 1) - 1), mx = 0; for (int i = 1, j = 1; i <= n; i = j) { while (j <= n && (a[i] & msk) == (a[j] & msk)) j++; mx = std::max(mx, j - i); } if (mx <= 2) { for (int i = 1; i < n; i++) { if ((a[i] & msk) != (a[i + 1] & msk)) continue; c[++k] = a[i] ^ a[i + 1]; } break; } } for (int w_ = w; ~ w_; w_--) { k_ = 0; for (int i = 1; i <= k; i++) { if (~ c[i] >> w_ & 1) { c_[++k_] = c[i]; } } if (k_ == 0) { c0[w_] = 1; } else { k = k_; memcpy(c + 1, c_ + 1, sizeof(int) * k); } } ll ans = 0; for (int i = 1; i <= m; i++) { int u = 0; for (int w_ = 29; ~ w_; w_--) { if (! c0[w_]) { u = son[u][b[i] >> w_ & 1]; } else { ans += siz[son[u][b[i] >> w_ & 1]]; u = son[u][~ b[i] >> w_ & 1]; } if (! u) break; } if (u) ans += siz[u]; u = 0; for (int w_ = 29; ~ w_; w_--) { int &v = son[u][b[i] >> w_ & 1]; if (! v) v = ++tn; siz[u = v]++; } } printf("%lld\n", ans); return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧