模型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
1≤n,k≤100
1
≤
s
i
,
h
i
≤
10000
1≤si,hi≤10000
1≤si,hi≤10000
大概时间复杂度: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 1≤n,ai≤100
#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
1≤n≤1×105
1
≤
a
i
≤
1
×
1
0
6
1≤a_i≤1×10^6
1≤ai≤1×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;
}