NIM博弈&&SG函数(HDU1848/HDU1524)

概述

这篇博客针对满足如下情形的博弈展开
  • A、B交替进行某种游戏规定的操作,每操作一次,选手可以在有限的操作(操作必须合法)集合中任选一种
  • 对于游戏的任何一种可能的局面,合法的操作集合只取决于这个局面本身,不取决于其它因素(跟选手,以前的所有操作无关)
  • 如果当前选手无法进行合法的操作,则为负

SG函数

一个经典的拿石子的情景
  • 有一堆大小为 n n n的石子,现在双方开始轮流拿石子,每一次可以拿走位于集合 { 1 , 2 , 3 } \{1,2,3\} { 123}中的那么多个石子,轮到谁不能拿了谁就输。
          这种情景比较直观的一个想法是,可以通过动态规划求解。设 d p [ i ] dp[i] dp[i]表示当前石子的数量为 i i i时是否能赢,则状态转移方程为 d p [ i ] = ! ( d p [ i − 1 ] & & d p [ i − 2 ] & & d p [ i − 3 ] ) dp[i]=!(dp[i-1] \&\& dp[i-2] \&\& dp[i-3]) dp[i]=!(dp[i1]&&dp[i2]&&dp[i3]),这种想法很直观,如果当前状态是必胜态,那么必定在拿走某数量的石子后可以将状态转移为必败态。
SG函数
  • 先定义 m e x ( m i n i m a l e x c l u d a n t ) mex(minimal excludant) mex(minimalexcludant)运算,这是施加于一个集合的运算,表示最小的不属于这个集合的非负整数。例如 m e x { 0 , 1 , 2 , 4 } = 3 、 m e x { 2 , 3 , 5 } = 0 、 m e x { } = 0 mex\{0,1,2,4\}=3、mex\{2,3,5\}=0、mex\{\}=0 mex{ 0,1,2,4}=3mex{ 2,3,5}=0mex{ }=0
  • 对于任意状态 x x x , 定义 S G ( x ) = m e x ( S ) SG(x) = mex(S) SG(x)=mex(S),其中 S S S x x x后继状态的 S G SG SG函数值的集合。如 x x x有三个后继状态分别为 a , b , c a,b,c a,b,c,那么 S G ( x ) = m e x { S G ( a ) , S G ( b ) , S G ( c ) } SG(x)=mex\{SG(a),SG(b),SG(c)\} SG(x)=mex{ SG(a),SG(b),SG(c)}。 这样集合 S S S的终态必然是空集,所以 S G SG SG函数的终态为 S G ( x ) = 0 SG(x)=0 SG(x)=0。当且仅当 x x x为必败态 P P P时, S G ( x ) = 0 SG(x)=0 SG(x)=0
SG函数的意义
  • 对于任何一个状态 x x x来说,如果 S G ( x ) ! = 0 SG(x)!=0 SG(x)!=0,说明这个状态可以通过某种操作转移到必败态,这时对手将必败;否则对手将必胜。这是从布尔意义来对 S G SG SG函数进行的解释。
  • 但是 S G SG SG函数并没有像前面用动态规划思想分析的那样,只定义两种取值。实际上, S G SG SG函数还有更巧妙的性质,如果 S G ( x ) = k SG(x)=k SG(x)=k,那么意味着 x x x后面的状态的 S G SG SG函数值是 [ 0 , k − 1 ] [0,k-1] [0,k1]中的全部整数。这个性质的巧妙会在NIM博弈中体现出来。

NIM博弈

      上面的取石子的游戏情景如果变成了给出 k k k堆石子,每次可以选任意一堆取一定数量个的话,这个问题的解就是所有石子堆中的石子数量的 S G SG SG函数的异或值。如果异或值非0则先手必胜;反之,先手必败。
      我们可以这样想这个问题,如果异或值为0,那么无论先手取多少数量都一定会导致异或值不为0,这时候,后手只需要取一定数量的石子使得异或值保持为0。随着石子数量的减少,异或值始终会在后手变为0,因此先手必胜。
      如果异或值非0,设这个非零值为 x x x。那么对于非0值的最高位的1来说(不妨设为 k k k,这个数字为 n n n)。我们可以拿掉一定的数量,使得剩余值等于 x x x,这样剩下的异或值就是0。

HDU1524 AC代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1010;
int sg[maxn], vis[maxn], n, m;
vector<int> b[maxn];
int mex(set<int> &s){
    
    
	for(int i=0;i<=s.size();++i) 
		if(s.find(i) == s.end()) return i;
}
int getSG(int cur){
    
    
	int i;
	if(b[cur].empty()){
    
    
		vis[cur] = 1;
		return 0;
	}
	if(vis[cur]) return sg[cur]; 
	int hash[maxn] = {
    
    0};
	for(i=0;i<b[cur].size();++i){
    
    
		hash[getSG(b[cur][i])] = 1;
	}
	vis[cur] = 1;
	for(i=0;i<=n;++i){
    
    
		if(!hash[i]){
    
    
			sg[cur] = i;
			break;
		}
	}
	return sg[cur];
}
int main(){
    
    
	int t, i, j, x;
	ios::sync_with_stdio(false);
	while(cin>>n){
    
    
		memset(sg, 0, sizeof(sg));
		memset(vis, 0, sizeof(vis));
		for(i=0;i<n;++i) b[i].clear();
		for(i=0;i<n;++i){
    
    
			cin>>x;
			for(j=0;j<x;++j){
    
    
				cin>>t;
				b[i].push_back(t);
			}
		}
		for(i=0;i<n;++i) getSG(i);
		while(cin>>m){
    
    
			if(!m) break;
			int ans = 0;
			for(i=0;i<m;++i){
    
    
				cin>>j;
				ans ^= sg[j];
			}
			if(ans){
    
    
				cout<<"WIN\n";
			}else{
    
    
				cout<<"LOSE\n";
			}
		}	
	} 
	return 0;
} 

HDU1848 AC代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1010;
int sg[maxn], f[maxn];
void getSG(){
    
    
	int i, j;
	f[1] = 1; 
	f[2] = 2;
	for(i=3;i<=1000;++i){
    
    
		f[i] = f[i-1] + f[i-2];
		if(f[i] > 1000) break;
	}
	int sz = i;
	for(i=1;i<=1000;++i){
    
    
		int vis[maxn] = {
    
    0};
		for(j=1;j<sz;++j){
    
    
			if(i >= f[j]){
    
    
				vis[sg[i - f[j]]] = 1;
			}
		}
		for(j=0;j<=1000;++j){
    
    
			if(!vis[j]){
    
    
				sg[i] = j;
				break;
			}
		}
	}
} 
int main(){
    
    
	int n, m, p;
	ios::sync_with_stdio(false);
	getSG();
	while(cin>>m>>n>>p){
    
    
		if(!m && !n && !p) break;
		int ans = sg[m] ^ sg[n] ^ sg[p];
		if(ans){
    
    
			cout<<"Fibo\n";
		}else{
    
    
			cout<<"Nacci\n";
		} 
	}
	return 0; 
}

猜你喜欢

转载自blog.csdn.net/qq_42968686/article/details/108583986