关于sg函数打表的理解

博弈的题大多数用sg函数打表找规律
博弈的题大多数用sg函数打表找规律
博弈的题大多数用sg函数打表找规律
记忆话搜索可以更快
记忆话搜索可以更快
记忆话搜索可以更快

理解

定义sg值为0时表示后手必胜,sg为1时为先手必胜。
那么对于每一个人,都会去查找使得当前状态变成sg值为0的情况
那么就是说,对于多种情况,只会选择sg值最小的情况进行选择,遍历一下,看看以我为起点进行,最终到达的情况是如果是sg值为0,那么我就进行选择,否则只能任意选择

第一种打表写法。
从现状态开始进行查找。

  1. 对于必败方的情况都返回0,此时的必败状态就是一眼能看出来的情况,即不用博弈就知道先手必胜的情况。
  2. 然后令sg值为1,表示我必输状态,再取判断所有情况,得到所有情况的sg值,取最小值
  3. 转换状态:如果对于下一个人来说,当前sg为0,即后手必胜,即我胜利,返回值为1,否则sg值为1时,表示先手必胜,也就是我必输。
    然后打表找规律即可。

第二种打表写法
从先手必败的局势开始往后递推

2020ccpc绵阳的G题
有0123这4种数字a,b,c,d张,每次可以拿走两个数字,和≤3,然后用两个数字的和来代替,放回数字堆。两个人进行博弈
sg函数打表

int sg(int a, int b, int c, int d){
    if(a == 0 && b == 0) return 0;
    if(a == 1 && b == 0 && c == 0 && d == 0) return 0;
    if(a == 0 && b == 1 && c == 0) return 0;
    int f = 1;
    if(a > 0) f = min(f, sg(a - 1, b, c, d));
    if(b >= 2) f = min(f, sg(a, b - 2, c + 1, d));
    if(b > 0 && c > 0) f = min(f, sg(a, b - 1, c - 1, d + 1));
    return 1 - f;
}

然后直接打表得到规律。

#include <iostream>
#include <cstdio>
using namespace std;
int sg(int a, int b, int c, int d){
    if(a == 0 && b == 0) return 0;
    if(a == 1 && b == 0 && c == 0 && d == 0) return 0;
    if(a == 0 && b == 1 && c == 0) return 0;
    int f = 1;
    if(a > 0) f = min(f, sg(a - 1, b, c, d));
    if(b >= 2) f = min(f, sg(a, b - 2, c + 1, d));
    if(b > 0 && c > 0) f = min(f, sg(a, b - 1, c - 1, d + 1));
    return 1 - f;
}
bool check(int a, int b, int c, int d){
    if(a == 0 && b == 0 && c == 0 && d == 0) return 0;
    if(b == 0 && c == 0 && d == 0) {
        return a % 2 == 0;
    }
    if(a & 1) {
        if(b % 3 == 0) return 1;
        if(b % 3 == 1) return c == 0;
        if(b % 3 == 2) return c >= 2;
        return 1;
    }else {
        if(b % 3 == 0) return 0;
        if(b % 3 == 1) return c != 0;
        if(b % 3 == 2) return 1;
        return 1;
    }
    return 1;
}
int main(){
    int t;
    scanf("%d", &t);
    for(int i = 1; i <= t; i++) {
        int a, b, c, d;
        scanf("%d%d%d%d", &a, &b, &c, &d);
        printf("Case #%d: %s\n", i, check(a, b, c, d) ? "Rabbit" : "Horse");
    }
    return 0;
}

传送门
直接sg打表就行了,必败情况是n = 1时
因为考虑到答案是求≥n时结束,也相当于是除以某个数字向上取整的意思
记忆话搜索一下防止超时

#include <iostream>
#include <cstdio>
#include <map>
#define ll long long
using namespace std;
const int N = 1e5 + 4;
std::map<ll, bool> mp;
bool SG(ll n){
    if(n == 1) return 0;
    if(n <= 9) return 1;
    if(n <= 18) return 0;
    if(mp.count(n)) return mp[n];
    bool f = 1;
    for(int i = 2; i <= 9; i++) {
        f = min(f, SG((n + i - 1) / i));
    }
    return mp[n] = 1 - f;
}
int main(){
    ll n;
    while(~scanf("%lld", &n)){
        printf("%s\n", SG(n)? "Stan wins.":"Ollie wins.");
    }
    return 0;
}

传送门
这个可以用第二种打表方法进行
从先手必败的(0,0,0)进行递推

#include <iostream>
#include <cstdio>
using namespace std;
const int N = 305;
bool sg[N][N][N];
void init(){
    int a = 300, b = 300, c = 300;
    for(int i = 0; i <= 300; i++){
        for(int j = 0; j <= 300; j++){
            for(int k = 0; k <= 300; k++){
                if(!sg[i][j][k]){
                    for(int x = 1; x + i <= a; x++) sg[x + i][j][k] = 1;
                    for(int y = 1; y + j <= b; y++) sg[i][y + j][k] = 1;
                    for(int z = 1; z + k <= c; z++) sg[i][j][z + k] = 1;
                    for(int x = 1; x + i <= a && x + j <= b; x++) sg[i + x][j + x][k] = 1; 
                    for(int x = 1; x + i <= a && x + k <= c; x++) sg[i + x][j][k + x] = 1;
                    for(int x = 1; x + j <= b && x + k <= c; x++) sg[i][j + x][k + x] = 1; 
                }
            }
        }
    }
}
int main(){
    init();
    int a, b, c;
    while(cin >> a >> b >> c){
        printf("%d\n", sg[a][b][c]);
    }
    return 0;
}
posted @ 2020-11-02 22:25  Emcikem  阅读(1127)  评论(0编辑  收藏  举报