博弈论:Alice and Bob

题目链接:https://ac.nowcoder.com/acm/contest/11166/A

题目:

给你两堆石子,每个人可以从某一堆中取出k个(k > 0),从另一堆中取出s * k个 (s >= 0),

现Alice先去,Bob后取,到哪个人取不了了以后,哪个人就算输。

 思路:

首先我们知道,当面对{0,0}的情况的时候,必为输, 而在此基础上可以知道,f[0 + k][0 + s * k] 或者 f[0 + s * k][0 + k] 必赢。 同理,f[2][3]必为输,所以f[2 + k][3 + s * k] / f[2 + s * k][3 + k]必为赢,因为f[2 + k][3 + s * k]必能直接变为f[2][3]的情况。

以此类推。

于是进行初始化,f[i][j] 中 i 从 0~ 5000, j 从0~5000。同时还要枚举 i + k 和 j + s * k 的情况,

其中 i + k <= 5000,  j + s * k <= 5000. 进行初始化。

那么初始化完了之后呢?

给出两堆石子a,b ,当f[a][b]为1的时候,那么就可以确定其为必胜态了。 那么问题来了,f[a][b]为0的时候,是否就一定代表其会输呢?  这个是肯定的.无法使得f[a - k][b - s * k]也变为0.

因为如果f[a - k][b - s * k]为0的话,则 f[a - k + k][b - s * k + s * k]必为1.因为取走f[a - k + k][b - s * k + s * k]中取走 {k,s * k}即可,变为0.  所以相互矛盾.从而可以知道从f[a][b] == 0所能得到的值必为1.

那么还有一个问题:这样的话, 时间复杂度不久在0(n ^ 4)了嘛?必定过不了才对.

然而实际上并不是这样的. 

有这么一个结论需要知道,对于一个i只存在至多一种j后手能够获胜.

证明如下,如果f[i][j] 满足后手胜,f[i][t] 也满足后手胜, 且 t > j的话, 那么当f[i][t]的情况时,我们应该是后手胜,但是我们可以发现我们从第二堆中取出 t - j 个 则变为了f[i][t]  又变成了一个新的后手胜. 这就产生了矛盾.  所以可以知道 对于一个i 至多只存在一种j后手胜., 

那么这个结论有什么用呢?这个结论的用处就是每个i只有 最多一个胜利的状态, 那么我们就不需要对所以的状态进行处理了. 通过无法取胜的状态去 更新能够取胜的状态 可以大大降低时间复杂度, 变为O(n). 

则总的时间复杂度就由 O(n ^ 4)变为了O(n ^ 3 左右了). 

 代码实现:

// 此代码时间卡的特别紧
# include <iostream>
using namespace std;

const int N = 5010;

bool f[N][N];

int t; 

int main()
{
    f[0][0] = 0; // 输
    for(int i = 0 ; i <= 5000 ; i++)
    {
        for(int j = 0 ; j <= 5000; j++)
        {
            if(f[i][j] == 0)  // 使用输的状态去更新赢的状态,虽然内层有两个for循环,但是赢得状态总的不会超过5000.
            {
                for(int k = 1 ; i + k <= 5000 ; k++)
                {
                    for(int s = 0 ; j + s * k <= 5000 ; s++)
                    {
                        f[i + k][j + s * k] = 1;
                    }
                }
                for(int k = 1 ; j + k <= 5000 ; k++)
                {
                    for(int s = 0 ; i + s * k <= 5000 ; s++)
                    {
                        f[i + s * k][j + k] = 1;
                    }
                }
            }
        }
    }
    scanf("%d",&t);
    
    while(t--)
    {
        int a,b;
        scanf("%d %d",&a,&b);
        
        if(f[a][b])
        {
            printf("Alice\n");
        }
        else
        {
            printf("Bob\n");
        }
        
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值