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;
}
总结:
这只是很简单的博弈论模板题,还需多学习,多练习。