GYM 100500 E IBM Chill Zone 博弈 SG

题意: 给出n个石子, 两个人轮流取, 每个人只能取连续的k个, 不能取即输, 问在最优策略时, 第一个人会赢吗?

思路: 首先很容易想到dfs暴力穷举, 枚举当前这个人取的所有方式, 如果存在一种方式使得对手先手必败, 那么就是必胜态.

枚举的方式很简单: 因为是从now个连续石子中取k个, 那么每次操作后, 会剩下两堆分离的石子堆, 他俩可能是:(0个, now-k个), (1个, now-k-1个), ... , ( (now-k)/2个, now-k-(now-k)/2个)...只要存在一种取法使得下一个人输, 那么此状态就赢; 如果所有取法下一个人都赢, 那么此状态就输. 然而哪怕加上记忆化也要T, 就要用到新算法了.

SG函数的思想:

P态: 这个状态下, 先操作的人会输. N态: 这个状态下, 先操作的人会赢.

考虑这个题: 如果当前剩下的石子个数<k个, 那么这个状态就是P态, 如果剩下石子刚好是[k,2k-1]个,因为这些状态可以转移到P态, 那么[k,2k-1]就是N态. 而因为[2k,3k-1]只能到达N态, 所以这是P态.

此时, 我们定义没有后继状态的点(边界的必败点)为"0级胜态", 相应的, 转移到"0级胜态"的点为"1级胜态", 转移到"1级胜态"的点为"2级胜态"....

每个点如果能同时到达"0级胜态"~"n-1级胜态", 他就是一个"n级胜态", 而如果他能到达"0级胜态", "2级胜态", 却不能到达"1级胜态", 他就只能是"1级胜态". 换句话说, 这个点是几级胜态取决于他第一级不能到达的胜态.

这样, 我们就会发现, 只要这个点是除"0级胜态"外所有的胜态, 他就是必胜态, 反之("0级胜态")就是必败态. 有了这个思想, 我们就可以记忆化递归求解这类问题了:

(当前状态的所有)前导状态是几级胜态→当前状态是几级胜态

实际上在SG中, 几级胜态就是sg值.

这大体就是SG定理(Sprague-Grundy)的思路, 具体(为什么是这样&怎么来的)看这里.

此外, 我们注意到, 这个题每次取完都会让一堆石子分成了两堆(其中1(2)堆可能是0), 相当于游戏的剩余时间是在2堆石子中继续(取k个连续石子)的游戏. 而SG定理告诉我们, 子状态的(按此类取法的)sg值就是所有新分出堆的sg值的异或.(为什么?看那里这里)

总结以下怎么用: 求出每种取法下, 子状态的sg值, 放入一个集合, 当前状态的sg值就是不在这个集合中的最小的数.

代码:

#include<bits/stdc++.h>
#define fuck(x) std::cout<<"["<<#x<<"->"<<x<<"]"<<endl;
using namespace std;
typedef long long ll;

const int M=2e5+5;
const int inf=1e9+5;
const int mod=1e9+7;

int _,n,k,caz=0;
int SG[55];

int sg(int now) {
    if(now<k)
        return 0;
    if(SG[now]!=-1)
        return SG[now];

    int vis[55]= {0};
    for(int i=0; i<=(now-k)/2; i++) {
        vis[ sg(i) ^ sg(now-k-i)   ]=1;
    }
    for(int i=0;; i++) {
        if(vis[i]==0)
            return SG[now]=i;
    }
}


int main() {
    scanf("%d",&_);
    while(_--) {
        memset(SG,-1,sizeof(SG));
        scanf("%d%d",&n,&k);
        if(sg(n))
            printf("Case %d: Winning\n",++caz);
        else
            printf("Case %d: Losing\n",++caz);
    }
    return 0;
}

TP

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值