Tic-Tac-Toe-Nim
题意
有3*3的矩阵,两个人进行博弈,开始时双方都会选择一个值清零,然后先手继续。当一方操作完成后,如果当前行或列全是0就胜利。问先手第一次动作(也就是清零)操作有几种能保证必胜。
解
-
假设先手选择了(i, j),即第 i 行第 j 列的数字,那么后手选择的数字必不能是第i行或是第j列的。
-
在先手选择了(i1, j1), 后手选择了(i2, j2)进行清零后,(满足:i1 != i2 || j1 != j2),我们首先看(1, 1),(2, 2)的情况:
这时,先手和后手,谁先让(1, 2), (1, 3), (2, 1), (2, 3), (3, 1), (3, 2)清零,肯定会输。也就是谁让这几个位置变为0谁败。而(3, 3)可以被拿光也不会有影响。
这样,这个问题可以转化成有7堆石子,其中第7堆可以随便拿,其他堆谁先让石子数变成0谁就输。也就是这些堆的石子,谁让它数量变成1就不可以再拿了。
再转化一下,将其他六堆石子数-1,然后就变成7堆石子随便拿,谁没有石子拿谁就输,这就变成了标准的nim游戏。
而对于nim游戏,我们就可以直接通过异或和来运算结果,如果异或为0,表示先手必败,反之必胜。
对于其他情况,也就是开始两个人选择清零的不是(1, 1), (2, 2)的话,同样满足 i1 != i2 || j1 != j2.而由于我们只考虑行和列的影响,我们可以进行行交换和列交换,让他变成上面讨论的(1, 1), (2, 2)的情况。 -
前面讨论了先手和后手选择清零的位置之后的事情,那么回到问题,我们需要得到先手选择哪些位置清零还能够保证自己必胜。这样,对于3*3的矩阵,我们可以枚举每个点,在先手选择这个点清零后,后手选择其他任意情况都必须保证先手必胜才能让答案个数+1.同样暴力枚举后手的选择情况即可。
整体因为要枚举2个坐标,也就是4个值,用4层循环枚举i1, j1, i2, j2,再进行3*3的扫描判断,一共是O(36*T),T的范围是5e5,可以再加入一个判断,如果当前不满足必胜就直接跳出循环,这样的小优化即可。
#include<bits/stdc++.h>
using namespace std;
int a[4][4];
bool win(int i1, int j1, int i2, int j2)
{
bool vis[4][4];
memset(vis, 0, sizeof(vis));
int ans = a[6-i1-i2][6-j1-j2];
vis[i1][j1] = vis[i2][j2] = vis[6-i1-i2][6-j1-j2] = true;
for (int i = 1; i <= 3; i++)
{
for (int j = 1; j <= 3; j++)
{
if (!vis[i][j])
{
ans ^= (a[i][j] - 1);
}
}
}
return ans != 0;
}
int sol()
{
for (int i = 1; i <= 3; i++)
{
for (int j = 1; j <= 3; j++)
scanf("%d", &a[i][j]);
}
int ans = 0;
for (int i1 = 1; i1 <= 3; i1++)
{
for (int j1 = 1; j1 <= 3; j1++)
{
bool f = true;
for (int i2 = 1; i2 <= 3 && f; i2++)
{
if (i2 == i1) continue;
for (int j2 = 1; j2 <= 3 && f; j2++)
{
if (j2 == j1) continue;
if (!win(i1, j1, i2, j2))
f = false;
}
}
ans += (int)f;
}
}
return ans;
}
int main()
{
int T;
scanf("%d", &T);
while (T--)
printf("%d\n", sol());
}