题解/算法 {894. 拆分-Nim游戏}

题解/算法 {894. 拆分-Nim游戏}
@LOC: 1

LINK: https://www.acwing.com/problem/content/description/896/;

首先题意有点模糊…
给定一个集合{x1, x2, x3} (xi > 0), 一次操作: 选择一个元素x2, 选择两个数a,b 满足0 <= a,b < x2 (如果不存在这样的a,b 则失败), 然后新集合变成了 {x1, a, b, x3};

比如, {0}是必败的, 而{>0}都是必赢的 (因为可以变成{0,0});

可以看出是公平游戏, 证明他是 组合游戏: 对于局面{x1,x2,x3} 每次选择一个元素操作 不影响其他元素, 因此 每个元素 均是一个 独立的子游戏;

MARK: @LOC_0;
因此Get_SG( int _a)表示: 子游戏的局面是{a};
. 根据上面的分析, 其实 我们已经知道了他的胜败 (a=0必败, 否则a>0必胜), 但是 其实没什么用处…
因为我们要得到他的SG值 (理解这点很重要), 你不可以直接 O ( 1 ) O(1) O(1)判断 然后必败返回个 0 0 0 必胜则返回个 1 1 1, 这是错误的! 因此 我们要求的是整个游戏的输赢 而你当前这个局面 只是他的一个子游戏的局面, 他的输赢 与 整个游戏的输赢无关; 除非这个{a}局面 就是整个游戏的局面 那你可以这样做, 否则一定错误;
. 因此, 即便你知道了 子游戏的输赢状态, 没用 还是得求他的SG值;

这题还有一点特别, 通常 我们处理单独的子游戏 即Get_SG( _a), 那他所能衍生的所有局面 都是单独的子游戏, 可以这道题 他的子节点 会分裂成{a, b}, 也就是 由一个子游戏 又变成了 一个 组合游戏;
因此这里还要再次使用SG定理, 即子节点{a,b}的SG值 (一般子节点的SG值 是通过Get_SG函数的返回值 获得的) 但这里 由于子节点不是单独的子游戏 而是一个组合游戏, 所以再次根据SG定理 (即组合游戏的SG值 等于各个子游戏的SG值的异或和), 得到 子节点的SG值为Get_SG(a) ^ Get_SG( b);

@DELI;

int Get_SG( int _a){ // `a >= 0`;
    static int __record[ 102];
    { static bool __is_first = true; if( __is_first){ __is_first = false; memset( __record, -1, sizeof( __record));}}
    auto & ANS = __record[ _a];
    if( ANS != -1){ return ANS;}
    //--

    { // 检查终止态;
        bool is_leaf = false;
        if( _a == 0){
            is_leaf = true;
        }
        if( is_leaf){ return ANS = 0;}
    }

    unordered_set< int> mex;
    for( int a = 0; a < _a; ++a){
        for( int b = a; b < _a; ++b){
            auto aa = Get_SG( a);
            auto bb = Get_SG( b);
            mex.insert( aa ^ bb);
        }
    }
    //> 对`mex`进行MEX操作;
    for( ANS = 0; ; ++ANS){
        if( mex.find( ANS) == mex.end()){
            break;
        }
    }
    ASSERT_( ANS > 0);
    return ANS;
}

void __Solve(){
    cin>> N;
    int SG = 0;
    for( int i = 0; i < N; ++i){
        int a; cin>> a;
        SG ^= Get_SG( a);
    }
    if( SG == 0){
        cout<< "No"<< ED_;
    }
    else{
        cout<< "Yes"<< ED_;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值