取火柴游戏
题目链接:ybt高效进阶6-5-1 / luogu P1247
题目大意
给你 k 堆石子,第 i 堆 ni 个。
然后两个人轮流选一堆石子取走一定数量的石子,但不能不取。
谁没得取,谁就输了,问你先手必胜还是后手必胜。
如果是先手必胜,要输出第一次怎么取。(如果多种方案,就先取前面堆的石子)
思路
这个就是经典的 NIM 游戏模型了,首先给出定理:
当且仅当
n
1
x
o
r
n
2
x
o
r
.
.
.
x
o
r
x
k
=
x
≠
0
n_1\ xor\ n_2\ xor\ ...\ xor\ x_k=x\neq0
n1 xor n2 xor ... xor xk=x=0 的时候,先手必胜。
这里给一下证明:
首先,如果所有东西都没了,那就是先手必败(因为已经没有东西了),那显然异或得到的是
0
0
0。
那接着考虑任意一个局面,分为两种:异或得到的是
0
0
0 或者不是
0
0
0。
首先我们看不是
0
0
0 的情况,那
n
1
x
o
r
n
2
x
o
r
.
.
.
x
o
r
x
k
=
x
≠
0
n_1\ xor\ n_2\ xor\ ...\ xor\ x_k=x\neq0
n1 xor n2 xor ... xor xk=x=0,设
x
x
x 的二进制表示下最高位的
1
1
1 是第
k
k
k 位,那至少有一堆石子
n
i
n_i
ni 它的第
k
k
k 位是
1
1
1。那也就是说,对于这一堆石子,会有:
n
i
x
o
r
x
<
n
i
n_i\ xor\ x<n_i
ni xor x<ni。
那我们可以从
n
i
n_i
ni 中取若干个(
n
i
−
(
n
i
x
o
r
x
)
n_i-(n_i\ xor\ x)
ni−(ni xor x))石子,使得它变成
n
i
x
o
r
x
n_i\ xor\ x
ni xor x,这样子就变成了异或得到的是
0
0
0 的局面。
那如果是 0 0 0,就无论你怎么取石子,得到的最终都不是 0 0 0。(如果你要最终得到的是 0 0 0 就要不取但是不能不取)
那就证明了结论。
然后至于找方案你就直接从左往右找第一个满足 n i x o r x < n i n_i\ xor\ x<n_i ni xor x<ni 的,然后就按着那个减的方法搞。
代码
#include<cstdio>
using namespace std;
int k, n[500001], ans;
int main() {
scanf("%d", &k);
for (int i = 1; i <= k; i++) {
scanf("%d", &n[i]);
ans ^= n[i];
}
// if (!ans) printf("Lose");//一本通就是这个
if (!ans) printf("lose");//luogu就是这个
else {
for (int i = 1; i <= k; i++)
if ((n[i] ^ ans) < n[i]) {
printf("%d %d\n", n[i] - (n[i] ^ ans), i);
n[i] -= (n[i] - (n[i] ^ ans));
break;
}
for (int i = 1; i <= k; i++)
printf("%d ", n[i]);
}
return 0;
}