← Back to Blog
Competitive ProgrammingGame TheoryDigit DP
CF2217F Interval Game
Nim + XOR 加法恆等式 + Digit DP
看起來像機率 + 區間問題,核心是把遊戲轉成 Nim,再做 XOR 分佈的 counting。
2026-04-08 · Problem Link
問題重述
Input:多組測資,每組給兩個整數 ()。
遊戲流程:
- Alice 選一個區間
- Bob 均勻隨機選一個區間
- 兩人用這兩個區間玩 Nim:每回合可以選一個區間,把 縮小或把 放大,不能動的人輸。Alice 先手。
Output:輸出 Alice 應該選的 ,使她的勝率最高。
觀察:這是 4 堆 Nim。堆的大小分別是 ,每堆代表這個方向還能擴展多少次。
Nim 的結論:先手贏 ⟺ 四堆 XOR ≠ 0。Alice 是先手,所以 XOR = 0 時 Alice 輸。
Alice 要選一組 ,讓 Bob 隨機選完之後,四堆 XOR = 0(Alice 輸)的機率盡量小。 而 Bob 的可選範圍不受 Alice 的選擇影響,所以最小化機率等價於最小化滿足條件的組合數。
Step 1:轉成 XOR 問題
令:
Alice 輸 ⟺ ⟺ 。
令 ,Alice 的策略就是選一個 ,讓 Bob 滿足 的機率最小。
Step 2:定義 counting 問題
Bob 的合法範圍:。定義:
Alice 要在 中找到 最小的那個。
Step 3:關鍵代數轉換
利用經典恆等式:
令 ,則 ,條件變成:
把 的上界記作 ,也就是在固定 之後, 最大能取到多少。
第二個條件的原因:XOR 為 1 的 bit 代表 c 和 d 恰好一個是 1,AND 不可能同時也是 1。
每個 t 對應幾組 (c, d)?
固定 和 後,逐 bit 看:
| 的 bit | 的 bit | 的選擇 | 方案數 |
|---|---|---|---|
| 1 | 0(必須) | (1,0) 或 (0,1) | 2 |
| 0 | 0 | (0,0) | 1 |
| 0 | 1 | (1,1) | 1 |
中有 個 1-bit,每個貢獻 2 種選擇,所以:
cpp
// 對每個 s (= i):int mx = (x2 - i - 1) / 2; // M: t 的上界long long sum = digit(i, mx) * (1LL << __builtin_popcount(i));Step 4:Digit DP
現在需要數:只用 的 0-bits 組成的數字中, 的有幾個:
這就是標準的 Digit DP。從最高位到最低位掃:
- 遇到 的 1-bit: 必須放 0。 如果 這位是 1, 已經比 小了,後面的 free bits 全部自由 → 加上 。
- 遇到 的 0-bit(free bit): 可以放 0 或 1。 如果還在 tight 且 這位是 1 → 放 0 的分支不再 tight → 加上 ,放 1 繼續 tight。
每個 花 時間。
cpp
long long digit(int mask, int lim) { if (lim < 0) return 0; long long res = 0; bool flag = 1; for (int b = 19; b >= 0; b--) { bool m = (mask >> b) & 1; bool l = (lim >> b) & 1; if (flag && l) { int cnt = 0; for (int i = b - 1; i >= 0; i--) if (!((mask >> i) & 1)) cnt++; res += 1LL << cnt; if (m) flag = 0; } } if (flag) res++; return res;}Step 5:合併與枚舉
合併起來:
遍歷所有 ,取 最小的。 構造 Alice 的區間:取 ,也就是 。
Special Case
如果 ,可以選 。 此時 , Bob 不可能匹配 → Alice 必勝。
cpp
if (x1 > x2) { cout << x2 + 1 << " " << x1 << "\n"; return;}複雜度
完整 Code
cpp
#include <bits/stdc++.h>using namespace std;
long long digit(int mask, int lim) { if (lim < 0) { return 0; } long long res = 0; bool flag = 1; for (int b = 19; b >= 0; b--) { bool m = (mask >> b) & 1; bool l = (lim >> b) & 1; if (flag && l) { int cnt = 0; for (int i = b - 1; i >= 0; i--) { if (!((mask >> i) & 1)) { cnt++; } } res += 1LL << cnt; if (m) { flag = 0; } } } if (flag) { res++; } return res;}
void solve() { int x1, x2; cin >> x1 >> x2; if (x1 > x2) { cout << x2 + 1 << " " << x1 << "\n"; return; } int ans = 0; long long mn = 2e18; for (int i = 0; i < x1; i++) { long long sum = 0; if (i < x2) { int mx = (x2 - i - 1) / 2; sum = digit(i, mx) * (1LL << __builtin_popcount(i)); } if (sum < mn) { mn = sum; ans = i; if (!sum) { break; } } } cout << ans + 1 << " " << x1 << "\n";}
int main() { ios::sync_with_stdio(false); cin.tie(0); int t; cin >> t; while (t--) { solve(); }}解法 Flow
區間博弈 → 4 堆 Nim
↓先手輸 ⟺ ,找 最小的
↓,令
↓數 且 → Digit DP
↓,枚舉 取最小
CodeforcesNimXORDigit DPGame TheoryCompetitive Programming