2020 Multi-University Training Contest 10 Tic-Tac-Toe-Nim

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());
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值