跳转至

CSP-S 2019 初赛 取石子

题面:

img

img

代码:

// 一般,我们用bool f[64],f[i]表示有i个石子,先手是否有必胜策略
// 状态图,必败状态 <--->  必胜状态
// 对于i个石子,存在规则j,使得
// f[i - b[j]]=false 必败状态  -----通过规则j,转移到--->   f[i]=true必胜状态 
// i可以从很多的状态[i-b[j]]转移过来,只要有一个是必败的就可以,所以用或运算

// b[i]<=64,最多64个石子,仅需考虑f[i-1, ..., i-64]的取值即可
// 将此部分状态进行压缩
// status用于记录i个石子,i-1, ...,i-64 是否存在必胜策略
// 二进制右起第j位的值,表示[i-j]个石子是否有必胜策略

#include <bits/stdc++.h>

using namespace std;

const int maxn = 64;

int n, m;
int a[maxn], b[maxn];
unsigned long long status, trans;
bool win;

int main(){
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; ++i)
        scanf("%d%d", &a[i], &b[i]);

    // 以a[i]为关键字做冒泡, 对规则进行升序
    for(int i = 0; i < n; ++i)
        for(int j = i + 1; j < n; ++j)
            if (a[i] > a[j]){
                swap(a[i], a[j]);
                swap(b[i], b[j]);
            }

    // 状态初始化
    // 0000000000           0ull
    // 1111111111          ~0ull
    // 1111111110          ~0ull ^ 1 表示0个石子的情况,先手必败
    status = ~0ull ^ 1;    
    trans = 0;

    for(int i = 1, j = 0; i <= m; ++i){
        while (j < n && a[j] == i){         
            // 枚举到符合条件的规则
            // 把规则j维护到trans里,二进制新增 用或 |
            trans |= 1ull << (b[j] - 1);    
            ++j;
        }

        //  status         表示先手必胜的状态
        // ~status         表示先手必败的状态,从先手必败的状态转移过来(状态图)
        // ~status & trans 与一下,二进制&,都是1,就才是1
        // 结果是 1,或者是 0
        win = ~status & trans;               

        // 把当下状态的结果,添加到status的右起第一位
        // i个石子,必胜或者必定,添加到status
        status = (status << 1) ^ win;   
    }

    puts(win ? "Win" : "Loss");

    return 0;
}   

/*
输入:
1 1 
1 1
输出:
Win

输入:
1 2
1 1
输出:
Loss

*/