1 条题解
-
0
G. 棋盘上的芯片
时间限制: 秒
内存限制: MBAlice 和 Bob 有一个 行 列的矩形棋盘,每行恰好有一个芯片。
他们选择 和 (),裁剪棋盘保留第 列到第 列的部分。
然后两人轮流移动芯片,每次可以选择一个芯片向左移动任意正整数格(不能移出棋盘)。
无法移动者输。给出 个询问 ,对于每个询问,若 ,,问先手(Alice)是否获胜。
博弈论转化
这是一个 impartial combinatorial game。
每个芯片独立,整个游戏是这些独立游戏的 Nim 和。
第 行的芯片在保留 时,可移动步数为 (若 ),否则不在棋盘上。
因此,Nim 堆大小为 。根据 Nim 理论,先手获胜当且仅当所有堆大小的异或和不为 0。
定义:若 则 Bob 胜(输出
B),否则 Alice 胜(输出A)。
核心思路
我们需要对 个询问快速计算 。
每个 最多 位(,)。官方解法采用根号分治:
选择一个 ,用不同方法处理低 位和高 位。
统一框架:将询问拆成两个前缀查询
定义:
那么:
证明:
在 内当且仅当 且 不成立,因此异或中 内的贡献出现一次, 内的贡献出现两次(抵消),故上式成立。这样,每个询问拆成两个形式为 的子问题,其中 是某个左端点( 或 ), 是原询问的 。
我们将所有 按 分组,然后从大到小扫描 ( 到 ),维护当前已处理的 的芯片信息,对于每个 回答所有 的查询。
处理低 位
我们只关心 的低 位(模 的值)。
由于模 , 和 都只需考虑其模 的余数。
不同余数最多 种。在扫描 的过程中,维护一个数组
rem[0..2^K-1],rem[z]表示当前已处理的芯片中, 的芯片个数的奇偶性(因为异或只需要奇偶性)。对于查询 ,我们枚举所有可能的余数 ,若
rem[z]为奇数,则异或上 (模 取正)。
枚举量 每个查询。复杂度:。
处理高 位
高 位是 的值。
观察:当 从 下降到 时,对固定的 , 的高位只在 跨越某些边界时改变一次。
实际上,每 个数为一段,高位相同。具体来说,对于每个 ,考虑区间 ,其中 ,,,且 。
对于该区间内的 , 的高位为 (或 取决于具体定义,这里我们取 )。因此,在扫描 时,当 进入某个区间 ,我们需要将高位值 异或到所有以 为左端点的查询 的答案中(注意这里的 是扫描变量, 是查询的第二个参数)。
为了实现这一点,我们维护一个支持区间异或、单点查询的数据结构(如 Fenwick 树,用异或代替加法)。
具体操作:- 对每个芯片 ,将其覆盖的 个区间 ,执行:
change(l, diff)(区间 异或上diff)change(r+1, diff)(区间 再异或上diff,实现区间 的异或)
- 对于查询 ,直接
get(y)得到所有芯片对该查询的高位贡献的异或和。
复杂度:
- 每个芯片更新 次,每次 ,总 。
- 每个询问查询一次,。
参数选择
总复杂度为:
$$O\left(n + m + q \cdot 2^K + n \cdot \frac{m}{2^K} \log m + q \log m\right) $$令 ,,则 和 平衡时,。
由于 ,,但 取 时 ,仍可接受。最终复杂度 。
代码解析
const int N = 200043; const int K = 10; const int Z = 1 << K; // Z = 1024rem[Z]:记录每个余数模 的奇偶性。t[N]:Fenwick 树(异或树),维护高位贡献。change(l, diff):将区间 异或上diff。get(r):返回 的异或和。qs[x]:以 为左端点的查询编号(对应 )。qs2[x]:以 为右端点 的查询编号(对应 ,但 )。
主循环:
for (int i = m; i >= 1; i--) { // 处理所有在列 i 的芯片 for (int j = 0; j < cnt[i]; j++) { rem[i % Z]++; // 更新低位余数奇偶性 // 处理该芯片的高位区间更新 int r = i; while (true) { int l = r - Z + 1; l = max(1, l); if (l > r) break; int diff = (i - l) >> K; // 高位值 change(l, diff); change(r + 1, diff); r -= Z; } } // 回答 Q(i, L) 形式的查询(即左端点 = i) for (auto x : qs[i]) { int cur = (get(i)) << K; // 高位部分左移 K 位 // 低位部分枚举所有余数 for (int k = 0; k < Z; k++) { int dist = (k - i) % Z; if (dist < 0) dist += Z; if (rem[k] & 1) cur ^= dist; } ans[x] ^= cur; } // 回答 Q(i, L) 形式的查询(其中 i = R+1) for (auto x : qs2[i]) { int cur = (get(ls[x])) << K; for (int k = 0; k < Z; k++) { int dist = (k - ls[x]) % Z; if (dist < 0) dist += Z; if (rem[k] & 1) cur ^= dist; } ans[x] ^= cur; } }注意:
get(i)返回的是当前所有芯片对查询位置 的高位贡献异或和。- 低位部分直接枚举余数,用
rem[k] & 1判断奇偶。 cur是 的完整值(高位 后与低位异或),最终异或到ans[x]中。
最终输出
for (int i = 0; i < q; i++) if (ans[i] == 0) printf("B"); else printf("A");若 则 Bob 胜,否则 Alice 胜。
总结
这道题通过根号分治处理不同二进制位,结合离线扫描、Fenwick 树区间异或和枚举余数,在 时间内解决了 个 Nim 游戏询问。
代码中 是经验选择,平衡了低位枚举与高位分段更新的复杂度。 - 对每个芯片 ,将其覆盖的 个区间 ,执行:
- 1
信息
- ID
- 6210
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 8
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者