Gambling Nim
分类:
bitmask
math
matrices
probabilities
1.题意概述
- 给你 n(1≤n≤500000) 张卡片,每张卡片的两个面(正面 ai 反面 bi )都有写数字,每个面都有0.5的概率正面,卡牌正反面的概率相互独立,求把所有卡牌正面数字( ci=ai or bi )拿来玩Nim游戏,先手必胜的概率。
2.解题思路
什么是Nim游戏:
有若干堆石子,每堆石子的数量都是有限的,合法的移动是“选择一堆石子并拿走若干颗(不能不拿)”,如果轮到某个人时所有的石子堆都已经被拿空了,则判负(因为他此刻没有任何合法的移动)。
Nim游戏结论:所有数字异或和为0,先手必败。
因此,这个问题实际上是让我们计算有多少种情况使得所有正面朝上的数字异或和为0。我们不妨假设 S=a1⊕a2⊕...⊕an 并且 ci=ai⊕bi ,假设卡片 j1,j2,...jk 都是 bi 朝上,剩下卡片都是 ai 朝上,那么现在总的异或和就是 S⊕cj1⊕cj2⊕...⊕cjk ,我们目标就是要找到一些 ci 的子集,使得他们的异或和为 S (因为一个数与自身异或为0),又因为
ci=(ci⊕cj)⊕cj ,因此我们可以自由通过用 ci⊕cj 来代替 cj (因为 ci⊕cj=ci⊕(ci⊕cj) !),因此我们考虑如何简化 ci 集合:- 选择出二进制位某一个位为1的数 ci 。
- 使用 ci⊕cj 来替换所有的 ci 。
- 重复上述步骤。
最终,我们会得到包括k个0和n-k个数的集合,下面问题在于如何检测这个集合是不是 S 的集合?我们可以按数位来讨论,而所有子集的数量一定是
2k 对于每个 n−k 个不是零的数字概率就是 2k2n ,这是先手必败,那么先手必胜就是 2n−k−12n−k ,因为是减一,所以分数一定是最简的,无需化简!
3.AC代码
int cnt;
ll a[maxn], b[maxn], c[maxn],digit[110];
bool add(ll x) {
rep(i, 1, cnt + 1)
if ((digit[i] ^ x) < x)
x = digit[i] ^ x;
if (x) {
digit[++cnt] = x;
return 1;
}
return 0;
}
inline void solve() {
int n;
scanf("%d", &n);
ll res = 0;
rep(i, 1, n + 1) {
scanf("%I64d%I64d", &a[i], &b[i]);
c[i] = a[i] ^ b[i];
add(c[i]);
res ^= a[i];
}
if (add(res)) puts("1/1");
else {
ll ans = 1LL << cnt;
printf("%I64d/%I64d\n", ans - 1, ans);
}
}