博弈论学习

本文介绍了如何运用SG函数解决取石子游戏问题,通过实例演示了不同场景下的策略,如先手优势分析和记忆化搜索。重点讲述了如何避免时间复杂度过高的问题,并分享了几个简化版的解决方案,包括直接思维方法和模板化处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

SG函数:

其本质感觉就是DFS,然后根据理论直接异或就可以得出结果。但是使用sg函数的时候要考虑时间复杂度的问题,毕竟是递归。如果时间复杂度过高,则需要考虑是思维题否?

A - 取石子游戏 1

 

思路:这道题可以用sg函数做,但是这道题有更简单的方法:思维。我们可以发现,如果 n = ( 1 + k ) * r + s,当先手拿到s,而后手最多取a( a >= 1 && a <= k ),那么先手再取( 1 + k - a )。这样先手按照这个策略就会取得永远的胜利,如果 s == 0,那么不论先手取多少(在规则范围内),后手都永远占优势。 (注意这道题我最开始是想使用sg函数的,但是时间复杂度太高了,第一次的for( int i = 1; i < k + 1; i++ ),就要跑1e5……,后面还有各种递归更可怕)

#include<iostream>
using namespace std;
typedef long long ll;
ll n, k;
int main(){
    ios :: sync_with_stdio( false );
    cin.tie( NULL );
    cin >> n >> k;
    ll ans =  n % ( k + 1 );
    if( ans ) cout << 1;
    else cout << 2;
    return 0;
}

附上被T的代码(显示的Runtime Error,这种错误原因可能不只是数组越界)

#include<iostream>
#include<cstring>
#include<set>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
int n, k, ans, f[N];
int sg( int x ){
    set< int > st;
    if( f[x] != -1 ) return f[x];
    for( int i = 1; i < k + 1; i++ ){
        if( i <= x ) st.insert( sg( x - i ) );
        else break;
    }
    for( int i = 0; ; i++ ){
        if( !st.count( i ) ) return f[x] = i; 
    }
}
int main(){
    ios :: sync_with_stdio( false );
    cin.tie( NULL );
    cin >> n >> k;
    memset( f, -1, sizeof( f ) );
    int ans = 0;
    ans ^= sg( n );
    if( ans ) cout << 1;
    else cout << 2;
    return 0;
}

B - 取石子游戏 2

 

思路:就是使用sg模板题。

#include<iostream>
using namespace std;
typedef long long ll;
int main(){
    ios :: sync_with_stdio( false );
    cin.tie( NULL );
    ll res = 0, n, num;// 注意初始化res是 0;
    cin >> n;
    while( n-- ){
        cin >> num;
        res ^= num;
    }
    if( res ) cout << "win";// 当res的结果非零,表示先手必赢
    else cout << "lose";// 否则必输
    return 0;
}

D - S-Nim

思路:这道题其实和上面那道题是一样的,只是说一次取多少是有要求的。因为有多个局面所以我们一般情况下会进行清空操作,但是这道题不一样,我们会发现其实每次求出来的值,下次局面遇到相同的情况我们还是可以使用的,那么我们没有必要这样多开,只需要多开一个数组当做记忆数组就可以了,最开始多次memset( f, -1, sizeof( f ) );是因为对于sg函数的本质没有深刻的认识。(这道题可能会卡 cin / cout)

#include<iostream>
#include<set>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAX = 1e4 + 10;
int k, ak[MAX], m, n, an[MAX], f[MAX];
int sg( int x ){
    if( f[x] != -1 ) return f[x];// 说明找到直接退出
    int check[110];
    memset( check, 0, sizeof( check ) );
    for( int i = 0; i < k; i++ ){
        if( x >= ak[i] ) {// 判断条件
            sg( x - ak[i] );// 继续DFS搜索
            check[f[x - ak[i]]] = 1; // 标记
        }
    }
    for( int i = 0; ; i++ ){
        if( !check[i] ) return f[x] = i;
    }// 根据博弈论
}
int main(){
    while( ~scanf("%d", &k ) ){
        if( !k ) break;
        memset( f, -1, sizeof( f ) );
        for( int i = 0; i < k; i++ ) scanf("%d", &ak[i] );
     //   sort( ak, ak + k );
        scanf("%d", &m );
        while( m-- ){
            scanf("%d", &n );
            int ans = 0;
            for( int i = 0; i < n; i++ ){
                scanf("%d", &an[i] );
                ans ^= sg( an[i] ); // 相当于每堆就是一个图
            }
            if( ans ) printf("W");
            else  printf("L");
        }
        printf("\n");
    }
    return 0;
}

E - 拆数游戏

思路:最开始想用sg函数,但是发现写不了……(废材)。其实这就是个思维题:发现先手是偶数n采用最优策略就会赢,因为先手可以分解为 1 和 奇数(n - 1),那后者只能选择奇数,分解到最后就变为 1 1 后手任何数都不能再分解了。

#include<iostream>
using namespace std;
typedef long long ll;

int main(){
    ios :: sync_with_stdio( false );
    cin.tie( NULL );
    ll t, num;
    cin >> t;
    while( t-- ){
        cin >> num;
        if( !( num % 2 ) ) cout << "suantou\n";
        else cout << "huaye\n";
    }
    return 0;
}

总结:

这只是很简单的博弈论模板题,还需多学习,多练习。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值