NIM博弈

模型1:

P2197 【模板】nim游戏

甲,乙两个人玩 Nim 取石子游戏。

Nim 游戏的规则是这样的:地上有 n 堆石子(每堆石子数量小于 104),每人每次可从任意一堆石子里取出任意多枚石子扔掉,可以取完,不能不取。每次只能从一堆里取。最后没石子可取的人就输了。假如甲是先手,且告诉你这 n 堆石子的数量,他想知道是否存在先手必胜的策略。

#include<bits/stdc++.h>
using namespace std;

int main()
{
	int T;cin>>T;
	while(T--){
		int n; cin>>n;
		int x = 0;
		for(int i=0,t;i<n;++i){
			cin>>t;
			x ^= t;
		}
		if(x) puts("Yes");
		else puts("No");
	}
	return 0; 
}

模型2:

台阶-Nim游戏

现在,有一个n级台阶的楼梯,每级台阶上都有若干个石子,其中第i级台阶上有ai个石子(i≥1)。

两位玩家轮流操作,每次操作可以从任意一级台阶上拿若干个石子放到下一级台阶中(不能不拿)。

已经拿到地面上的石子不能再拿,最后无法进行操作的人视为失败。

问如果两人都采用最优策略,先手是否必胜。

#include<bits/stdc++.h>
using namespace std;

int main()
{  
    int n;cin>>n;
    int x = 0;
    for(int i=1,t;i<=n;i++){
        cin>>t;
        if(i&1) x^=t;
    }
    if(x) cout<<"Yes\n";
    else cout<<"No\n";
    return 0;
}

模型3:

集合-Nim游戏

给定n堆石子以及一个由k个不同正整数构成的数字集合S。

现在有两位玩家轮流操作,每次操作可以从任意一堆石子中拿取石子,每次拿取的石子数量必须包含于集合S,最后无法进行操作的人视为失败。

问如果两人都采用最优策略,先手是否必胜。

1 ≤ n , k ≤ 100 1≤n,k≤100 1n,k100
1 ≤ s i , h i ≤ 10000 1≤si,hi≤10000 1si,hi10000

大概时间复杂度:O(h)

#include<bits/stdc++.h>
using namespace std;

const int N = 1e5+7;
int s[N],f[N];
int n;

int sg(int x)   //记忆化搜索sg函数值
{
    if(f[x]!=-1) return f[x];
    unordered_set<int> st;
    for(int i=0;i<n;i++){
        if(x>=s[i]) st.insert(sg(x-s[i]));
    }
    for(int i=0;;i++){
        if(!st.count(i)) return f[x] = i;
    }
}

int main()
{
    scanf("%d",&n);
    for(int i=0;i<n;i++) scanf("%d",&s[i]);
    int k,x=0;
    scanf("%d",&k);
    memset(f,-1,sizeof(f));
    for (int i=0,t;i<k;i++){
        scanf("%d",&t);
        x ^=s g(t);
    }
    if(x) printf("Yes\n");
    else printf("No\n");
    return 0;
}

模型4:

拆分-Nim游戏

给定n堆石子,两位玩家轮流操作,每次操作可以取走其中的一堆石子,然后放入两堆规模更小的石子(新堆规模可以为0,且两个新堆的石子总数可以大于取走的那堆石子数),最后无法进行操作的人视为失败。

问如果两人都采用最优策略,先手是否必胜。

1 ≤ n , a i ≤ 100 1≤n,ai≤100 1n,ai100

#include<bits/stdc++.h>
using namespace std;
const int N = 1e2+7;

int f[N];

int sg(int x)
{
	if(f[x]!=-1) return f[x];
	unordered_set<int> st;
	for(int i=0;i<x;i++){
		for(int j=0;j<=i;j++)
			st.insert(sg(i)^sg(j));
	}
	for(int i=0;;i++){
		if(!st.count(i)) return f[x] = i;
	}
}

int main()
{
	int n;
	cin>>n;
	int x = 0;
	memset(f,-1,sizeof(f));
	for(int i=0,t;i<n;i++){
		cin>>t;
		x ^= sg(t);
	}
	if(x) printf("Yes\n");
	else printf("No\n");
	return 0;
}

例题1:

HDU - 1847

由于数据比较小,所以直接暴力求出每个独立游戏的sg函数值即可;
以下两种写法本质上是一样的,复杂度也差不多;

Solution1:

#include<bits/stdc++.h>
using namespace std;
const int N = 2000+7;

int sg[N];

int get_sg(int x){
    if(sg[x]!=-1) return sg[x];
    unordered_set<int> se;
    for(int i=0;;i++){
        int t = pow(2,i);
        if(t<=x) se.insert(get_sg(x-t));
        else break;
    }
    for(int i=0;;i++)
        if(!se.count(i)) return sg[x] = i;
}

int main()
{
    memset(sg,-1,sizeof sg);
    int n;
    while(cin>>n){
        if(get_sg(n)) cout<<"Kiki\n";
        else cout<<"Cici\n";
    } 
    return 0;
}

Solution2:

#include<bits/stdc++.h>
using namespace std;
const int N = 2000+7;

int sg[N],f[15];
bool st[N];

void init()
{
    for(int i=0;i<=12;i++) f[i] = 1<<i;
    for(int i=1;i<=1000;i++){
        memset(st,0,sizeof st);
        for(int j=0;f[j]<=i;j++){
            st[sg[i-f[j]]] = 1;
        }
        for(int j=0;;j++)
            if(!st[j]) {
                sg[i] = j; break;
            }
    }
}

int main()
{
    init();
    int n;
    while(cin>>n){
        if(sg[n]) cout<<"Kiki\n";
        else cout<<"Cici\n";
    } 
    return 0;
}

例题2:

HDU - 3980

按sg函数的定义直接暴力求每个状态的sg函数值即可,但是对于不同的IGC来说,当两者的m不同时,其对应的sg函数便不同,所以对于不同场次的游戏来说要把sg函数初始化;

#include<bits/stdc++.h>
using namespace std;
const int M = 2000+7;

int sg[N];
int n,m;

int get_sg(int x)
{
    if(x<=0) return 0;
    if(sg[x]!=-1) return sg[x];
    unordered_set<int> se;
    for(int i=m;i<=x;i++){
        se.insert(get_sg(i-m)^get_sg(x-i));
    }
    for(int i=0;;i++)
        if(!se.count(i)) return sg[x] = i;
}


int main()
{
    int T; cin>>T;
    int k=1;
    while(T--){
        cin>>n>>m;
        if(m>n){
            printf("Case #%d: abcdxyzk\n",k++);
            continue;
        }
        n-=m;
        memset(sg,-1,sizeof sg);   //每组的sg函数值不同
        if(get_sg(n)) printf("Case #%d: abcdxyzk\n",k++);
        else printf("Case #%d: aekdycoin\n",k++);
    }
    return 0;
}

例题3:

涛涛和策策的游戏

涛涛和策策打码累了的时候会聚在一起van游戏。
某一天他们又凑在一起玩游戏了,因为最近他们在学数学知识,所以就开始van博弈小游戏了。
他们写下n个数字,从策策开始两个人轮流进行操作,每次操作只能选择一个大于1的数字x,选择x的一个大于1的因数y,让x变为x/y。
谁先不能操作谁就输了。现在你需要判断出是哪个学长赢了游戏。
如果是策策赢了,输出"CC yyds!"
如果是涛涛赢了,输出"TT txdy!"

1 ≤ n ≤ 1 × 1 0 5 1≤n≤1×10^5 1n1×105
1 ≤ a i ≤ 1 × 1 0 6 1≤a_i≤1×10^6 1ai1×106

Solution

每个数中的质因子个数即为每堆的石子个数;所以既是取NIM博弈中的石子模型

#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N=1e6+5;
int sg[N],s[N];
const int n=1e6+2;
int primes[N],cnt=0;
bool st[N];
void init()
{
	for(int i=2;i<=n;i++){
		if(!st[i]) primes[cnt++] = i;
		for(int j=0; primes[j]*i <= n;j++){
			st[primes[j] * i] = 1;
			if(i%primes[j]==0) break;
		}
	}
}
int getsg(int x){
	if(x==1) return 0;
	if(x==2) return 1;
	int ans=0,sum=x;
	for(int i=0;primes[i]<=x/primes[i];i++) {
		while(sum%primes[i]==0) {
			sum/=primes[i];
			ans++;
		}
		if(sum==1) break;
	} 
	if(sum!=1) ans++;
	return ans;
}
int main()
{   
	init();
	int t;
	cin>>t;
	ll sum=0;
	for(int i=1;i<=t;i++) {
		int m;
		cin>>m;
		sum^=getsg(m);
	}
	if(sum) cout<<"CC yyds!"<<endl;
	else cout<<"TT txdy!"<<endl;
	return 0;
}

例题4:

P2575 高手过招

Solution1:

每一行游戏是相互独立的,对于每一行从最右端开始,将连续的棋子看作在一个台阶上,每个空位置为一个台阶,并且台阶计数要从0开始;每轮当一连串的棋子其中一个被移动时,移动不同的棋子,空位会产生在不同的位置,可以看作任意个棋子被移动到了下一个阶梯。至此,阶梯模型便构造出来了,解法就是:各个阶梯上的棋子个数是相互独立的游戏并且每个阶梯上的sg函数值就是棋子个数,并且只需要求奇数阶梯的sg异或和即可;

#include<bits/stdc++.h>
using namespace std;

bool st[22];

int main()
{
    int T; cin>>T;
    while(T--){
        int n;  cin>>n;
        int ans = 0;
        for(int i=1,k;i<=n;i++){
            cin>>k;
            memset(st,0,sizeof st);
            for(int j=0,t;j<k;j++) cin>>t,st[t] = 1;
            int x = 0,num=0,id=0;  // ***从第0阶台阶开始 ***
            for(int c=20;c>=1;c--){
                if(st[c]) num++;
                else {
                    if(id&1) x ^= (num); //由于是可以移动任意一个棋子,所以可以看成任意多个棋子到下一个阶梯
                    num=0;		//所以不能对2求余
                    id++;   //每个空格或每个连续的1当作一个台阶
                }
            }
            if(id&1) x ^= num;
            ans ^= x;
        }
        if(ans) cout<<"YES\n";
        else cout<<"NO\n"; 
    }
    return 0;
}

Solution2:

sg函数打表,由于只有20位,所以可以很方便的二进制枚举每一个可能的状态,然后对于每一个状态移动一个棋子所能到达的状态的sg函数的值标记为1,并且每个状态最多能到达另外19个状态,所以sg函数的值不会大于20;

#include<bits/stdc++.h>
using namespace std;
#define IO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)
const int N = 1<<20+7;

bool st[24]; //每个状态最多能到达另外19个状态,所以sg的值不会大于20;
int sg[N]; 

void get_sg()
{
    for (int i=0;i<(1<<20);i++){
        memset(st,0,sizeof st);
        for (int j=0;j<20;j++)
            if ((i>>j)&1){
                for (int k=j-1;k>=0;k--){
                    if (((i>>k)&1) == 0){
                        st[sg[i^(1<<j)^(1<<k)]] = 1;
                        break;
                    }
                }
            }
        for (int j=0;;j++)
            if (!st[j]){
                sg[i] = j;break;
            }
    }
}

int main()
{
    IO;
    get_sg();
    int T; cin>>T;
    while (T--){
        int n;  cin>>n;
        int ans = 0;
        for (int i=1,k;i<=n;i++){
            cin>>k;
            int x=0;
            for (int j=0,tt;j<k;j++){
                cin>>tt;
                x |= 1<<(20-tt);     //x即为第i行的初始状态;
            }
            ans ^= sg[x];
        }
        if (ans) cout<<"YES\n";
        else cout<<"NO\n"; 
    }
    return 0;
}

例题5:

P2148 [SDOI2009]E&D

两两一对,由于不能暴力求每对数的sg函数的值,所以先根据题意打表找规律:
打表Code:

#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;

map<PII,int> mp;

int sg(PII x)
{
    if(mp.count(x)) return mp[x];
    unordered_set<int> se;
    for(int i=1;i<x.first;i++){
        se.insert(sg({i,x.first-i}));
    }
    for(int i=1;i<x.second;i++){
        se.insert(sg({i,x.second-i}));
    }
    for(int i=0;;i++){
        if(!se.count(i)) return mp[x] = i;
    }
}

int main()
{
    for(int i=1;i<=25;i++){
        for(int j=1;j<=25;j++){
            cout<<sg({i,j})<<" ";
        }
        cout<<"\n";
    }
    return 0;
}

Solution1:

规律1:当a、b同时是奇数时,sg(a,b) = 0,当只有一个数是奇数时 sg(a,b) = sg(a+1,b)、或sg(a,b) = sg(a,b+1)当两个数都是偶数时 sg(a,b) = sg(a/2,b/2)+1; 由此也可log级别求出sg函数值;

#include<bits/stdc++.h>
using namespace std;
#define IO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)

int sg(int a,int b){
    int ans=0;
    while(true){
        if((a&1)&&(b&1)) return ans;
        if(a&1) a++;
        else if(b&1) b++;
        else {
            a/=2;b/=2;
            ans++;
        }
    }
}

int main()
{
    IO;
    int T; cin>>T;
    while(T--){
        int n; cin>>n;
        int sum = 0;
        for(int i=1,x,y;i<=n/2;i++){
            cin>>x>>y;
            sum ^= sg(x,y);
        }
        if(sum) cout<<"YES\n";
        else cout<<"NO\n";
    }
    return 0;
}

Solution2:

规律2 :sg(a,b) 的值等于 a-1和 b-1的二进制表示下相同位置同时为0的位置。这样就能几乎O(1)求出sg函数值。

#include<bits/stdc++.h>
using namespace std;
#define IO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)

int sg(int a,int b){
    int k = (a-1)|(b-1);
    int ans=0;
    while(k&1) k>>=1,ans++;
    return ans;
}

int main()
{
    IO;
    int T; cin>>T;
    while(T--){
        int n; cin>>n;
        int sum = 0;
        for(int i=1,x,y;i<=n/2;i++){
            cin>>x>>y;
            sum ^= sg(x,y);
        }
        if(sum) cout<<"YES\n";
        else cout<<"NO\n";
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值