[博弈论]取石子

https://www.acwing.com/problem/content/1323/

在这里插入图片描述
思路:
感觉一定要很聪明脑洞大开的那种才能想出来

记忆化搜索

先考虑一种相对简单的情况。
假设题目给出的所有堆的个数都大于1。
设 总操作数 b = 堆数 + 石子总数 - 1,想到 b 为 奇数的时候 先手必胜。
在这里插入图片描述

简单情况时,始终保证所有堆的个数大于1,若某堆的石子个数=1了,则将这堆和别的堆合并,给到对面选手的状态,还时保持了所有堆的个数大于1.

设石子个数为1的堆有a个
定义 f ( a , b )
① 后继状态,从a中拿一个,f(a-1,b),如果是sg = 0的话,代表当前是必胜态。
② 后继状态,从>1的堆中拿一个,f(a,b-1),操作数减1,如果sg=0的话,代表当前是必胜态
③ 后继状态,两个>1的堆合并,f(a,b-1),操作数减1,如果sg=0的话,代表当前是必胜态
④ 后继状态,两个=1的堆合并,f(a-2,b+(3 或 2) ,正常是3,如果b=0的话等于2,因为定义时是-1,如果b本身有值,就代表已经减过了不用减了。
④ 后继状态,一个=1的堆和>1的堆合并,f(a-1,b+1),b中某个堆多了1。若sg=0,则当前必胜。

处理边界:
如果a = 0,则就回到上面的简单情况 b为奇数先手必胜
如果b = 1,则代表当前只剩1堆了,并且这堆里只剩下1,所以返回dp(a+1,0),a中多了一堆,b中没有堆了。

#include<bits/stdc++.h>
using namespace std;
const int N = 55,M = 50050;

int f[N][M];
int t,n;
int dp(int a,int b){
    int &v = f[a][b];
    if(v != -1) return v;
    if(a == 0) return v = b % 2;
    if(b == 1) return v = dp(a+1,0);
    
    if(a && !dp(a-1,b)) return v = 1;
    if(b && !dp(a,b-1)) return v = 1;
    if(a >= 2 && !dp(a-2,b + (b?3:2))) return v = 1;
    if(a && b && !dp(a-1,b+1)) return v = 1;
    
    return v = 0;
}
int main(){
    scanf("%d",&t);
    memset(f,-1,sizeof f);
    while(t--){
        scanf("%d",&n);
        int a = 0,b = 0;
        for(int i=1;i<=n;i++){
            int x;
            scanf("%d",&x);
            if(x == 1) a++;
            else b += b? x+1:x;
        }
        if(dp(a,b)) puts("YES");
        else puts("NO");
    }
    return 0;
}
posted @   Isaac233  阅读(110)  评论(0编辑  收藏  举报
编辑推荐:
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
阅读排行:
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
点击右上角即可分享
微信分享提示