花了两天写博弈论,16道题差不多了,阶梯尼姆还存在点问题,有机会碰到了再研究。sg函数大概明白了,真就是个暴力?
Calendar Game——HDU - 1079
链接:https://acm.hdu.edu.cn/showproblem.php?pid=1079
大意
给定1900.1.1日到2001.11.4日中的一天,Adam和Eve轮流跳转日期,adam先跳,谁先到2001.11.4日谁赢。跳转日期有两种跳法:1、跳转到当前日期的下一天;2、跳转到下个月的对应日,如不存在则只能选择1跳转。
思路
sg函数标记必胜点,处理日期有点麻烦,处理好了暴力dfs就行。
数据很水,代码没处理年份都能过。。。(已修改 )
CODE
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e5+7;
int rq[]={0,31,28,31,30,31,30,31,31,30,31,30,31};
int f[3000][20][50];
int solve(int n,int y,int r)
{
if(f[n][y][r]!=-1)
{
return f[n][y][r];
}
if(r<rq[y])
{
if(solve(n,y,r+1)==0)
return f[n][y][r]=1;
}
else if(y==2&&((n%4==0&&n%100!=0)||n%400==0)&&r==28)
{
if(solve(n,y,r+1)==0)
return f[n][y][r]=1;
}
else if(y==12&&r==31)
{
if(solve(n+1,1,1)==0)
return f[n][y][r]=1;
}
else
{
if(solve(n,y+1,1)==0)
return f[n][y][r]=1;
}
if((n==2001&&y==10&&r<=4)||(n<2001)||(n==2001&&y<10))
{
if(rq[y%12+1]>=r)
{
if(solve(n,y+1,r)==0)
return f[n][y][r]=1;
}
else if(y==1&&r==29&&((n%4==0&&n%100!=0)||n%400==0))
{
if(solve(n,y+1,r)==0)
return f[n][y][r]=1;
}
else if(y==12)
{
if(solve(n+1,1,r)==0)
return f[n][y][r]=1;
}
}
return f[n][y][r]=0;
}
int main()
{
ios::sync_with_stdio(0);
memset(f,-1,sizeof f);
int t;
cin>>t;
f[2001][11][4]=0;
while(t--)
{
int n,y,r;
cin>>n>>y>>r;
if(solve(n,y,r)==0)
{
cout<<"NO"<<endl;
}
else
{
cout<<"YES"<<endl;
}
}
return 0;
}
Public Sale——HDU-2149
链接:https://acm.hdu.edu.cn/showproblem.php?pid=2149
大意
物品成本价M,每个人每次加价幅度为1~N,当价格大于或等于成本价时获得胜利。
思路
巴什博弈板题,m%(1+n)== 0时先手必输,否则存在先手获胜拿法,全部输出即可。
CODE
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e5+7;
int n,m;
int main()
{
//ios::sync_with_stdio(0);
while(cin>>m>>n)
{
if(m<=n)
{
for(int i=m;i<=n;i++)
{
if(i==m) cout<<i;
else cout<<" "<<i;
}
cout<<'\n';
}
else
{
if(m%(n+1)==0)
cout<<"none"<<endl;
else
{
int temp=0;
for(int i=1;i<=n;i++)
{
if((m-i)%(n+1)==0)
{
if(!temp) temp++,cout<<i;
else cout<<' '<<i;
}
}
cout<<'\n';
}
}
}
return 0;
}
Euclid’s Game——HDU-1525
链接:https://acm.hdu.edu.cn/showproblem.php?pid=1525
大意
给定两个数a,b,两个人每次可用大的值减去小的值的数倍(差不能为负数),最先减到0的获胜。
思路
如果a>=b,且a<2b,那么走法固定(a-b),如果a>2b,那么当前玩家便可以根据后面的固定的走法选择走必赢的一步。
CODE
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
const int N=1e6+7;
int n,m;
int main()
{
ios::sync_with_stdio(0);
while(cin>>n>>m)
{
if(n==0&&m==0)break;
if(n<m) swap(n,m);
if(n%m==0)
{
cout<<"Stan wins"<<'\n';continue;
}
int temp=1;
while(1)
{
if(m==0||n%m==0||n>=2*m)
break;
n-=m;
swap(n,m);
temp=!temp;
}
if(temp)
cout<<"Stan wins"<<'\n';
else
cout<<"Ollie wins"<<'\n';
}
return 0;
}
Being a Good Boy in Spring Festival——HDU-1850
链接:https://acm.hdu.edu.cn/showproblem.php?pid=1850
大意
有m堆扑克牌,每次可以选择一堆并取走其中任意张牌,最后一次取牌的人获胜,问先手想赢的话第一步有几种选择。
思路
尼姆博弈板题,将当前堆数目减少到与其他所有堆数目异或和的异或和为0,则一定胜利,遍历寻找即可。
CODE
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e6+7;
int n,m;
int a[N];
int main()
{
ios::sync_with_stdio(0);
while(cin>>m)
{
if(m==0)break;
ll ans=0;
for(int i=1;i<=m;i++)
{
cin>>a[i];
ans^=a[i];
}
ll cnt=0;
for(int i=1;i<=m;i++)
{
if(a[i]>(ans^a[i]))
{
cnt++;
}
}
cout<<cnt<<endl;
}
return 0;
}
Play a game——HDU-1564
链接:https://acm.hdu.edu.cn/showproblem.php?pid=1564
大意
给定一个n*n的矩阵,第一个点在最角落,每人每次走一格,走过的格子不能再走,最后不能再走的人判负,输出获胜的人
思路
找规律题,剩余格子为奇数时先手必胜
CODE
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
const int N=1e6+7;
int n,m;
int main()
{
ios::sync_with_stdio(0);
while(cin>>n)
{
if(n==0)
break;
if((n*n-1)%2==0)
cout<<"ailyanlu"<<endl;
else
cout<<"8600"<<endl;
}
return 0;
}
取(m堆)石子游戏——HDU-2176
链接:https://acm.hdu.edu.cn/showproblem.php?pid=2176
大意
给定m堆石子数量,每次从一堆石子里面取任意个,最后一个取的人获胜。
思路
尼姆博弈,将当前堆数目减少到与其他所有堆数目异或和的异或和为0,则一定胜利,遍历寻找每堆石子是否满足条件,满足则输出。
CODE
#include<iostream>
#include<cstring>
using namespace std;
int a[1000009];
int main()
{
int m;
while(~scanf("%d",&m))
{
if(m == 0)
{
return 0;
}
memset(a,0,sizeof(a));
for(int i=0;i<m;++i)
{
scanf("%d",&a[i]);
}
int sum = 0;
for(int j = 0;j < m;++j)
{
sum = sum^a[j];
}
if(sum == 0)
{
printf("No\n");continue;
}
else{
printf("Yes\n");int u=0;
for(int j = 0;j < m;++j)
{
u = sum;
u^=a[j];
if(u<a[j])
{
printf("%d %d\n",a[j],u);
}
}
}
}
return 0;
}
Brave Game——HDU-1846
链接:https://acm.hdu.edu.cn/showproblem.php?pid=1846
大意
n个石子,每次可以取走1~m个,问胜者
思路
巴什博弈,不用脑子直接无脑敲
CODE
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e6+7;
int main()
{
ios::sync_with_stdio(0);
int t;
cin>>t;
while(t--)
{
int n,m;
cin>>n>>m;
if(n%(m+1))
{
cout<<"first"<<endl;
}
else
{
cout<<"second"<<endl;
}
}
return 0;
}
取石子游戏——HDU-1527
链接:https://acm.hdu.edu.cn/showproblem.php?pid=1527
大意
两堆石子,每次可以从一堆里取出任意石子或从两堆中取出相同数目石子,问胜者。
思路
威佐夫博弈,赞美完美比例!当n>m,如果(m-n)* (int)(((sqrt(5.0)+1)/2)==n则为必输态,否则为必胜态。
CODE
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e6+7;
int main()
{
ios::sync_with_stdio(0);
int n,m;
while(cin>>n>>m)
{
if(n>m)
swap(n,m);
if((int)(((sqrt(5.0)+1)/2)*(m-n))==n)
{
cout<<0<<endl;
}
else
{
cout<<1<<endl;
}
}
return 0;
}
Good Luck in CET-4 Everybody!——HDU-1847
链接:https://acm.hdu.edu.cn/showproblem.php?pid=1847
大意
一共n张牌,每人每次抓牌的个数只能是2的幂次,谁能赢。
思路
sg函数打表发现规律为3的倍数必败
CODE
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e6+7;
int main()
{
ios::sync_with_stdio(0);
int n,m;
while(cin>>n)
{
if(n%3)
{
cout<<"Kiki"<<endl;
}
else{
cout<<"Cici"<<endl;
}
}
return 0;
}
取(2堆)石子游戏——HDU-2177
链接:https://acm.hdu.edu.cn/showproblem.php?pid=2177
大意
两堆石子,每次从一堆拿任意个或两堆拿相同个,若先手必胜,输出先手的取法
思路
威佐夫博弈,完美比例点必败,否则存在两种输出情况,从多的一堆取走一些或者从两堆同时取走一些。
CODE
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e6+7;
int main()
{
ios::sync_with_stdio(0);
int n,m;
while (cin>>n>>m)
{
if(!n&&!m)break;
if(n>m)swap(n,m);
int p=m-n;
if((int)((sqrt(5.0)+1)/2*p)==n)
{
cout<<0<<endl;
}
else
{
cout<<1<<endl;
cout<<(int)((sqrt(5.0)+1)/2*p)<<' '<<(int)((sqrt(5.0)+1)/2*p)+p<<endl;
for(int i=0;i<n;i++)
{
p=i*(1+sqrt(5.0))/2;
if(n-i==p)
{
printf("%d %d\n",n-i,n);
break;
}
}
}
}
return 0;
}
取石子游戏——HDU-2516
链接:https://acm.hdu.edu.cn/showproblem.php?pid=2516
大意
一堆n个石子,先取者第一次可以取除全部取完任意多个石子,以后每次取不超过上次取子数的2倍,先取完者胜利。
思路
斐波拉契博弈,值存在于斐波拉契数列中的为必败态。
CODE
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e6+7;
int main()
{
ios::sync_with_stdio(0);
int n;
while (cin>>n,n)
{
ll p=0,x=1,y=1;
while(p<n)
{
p=x+y;
swap(x,y);
y=p;
}
if(p==n)
{
cout<<"Second win"<<endl;
}
else
{
cout<<"First win"<<endl;
}
}
return 0;
}
A Multiplication Game——HDU-1517
链接:https://acm.hdu.edu.cn/showproblem.php?pid=1517
大意
两个人操作p=1开始乘,每次乘2~9的数,谁先大于等于n谁胜。
思路
sg函数打表找规律,发现1~9(1 * 9)必胜,10 ~18(1 * 9 * 2)必败,19 ~ 162(1 * 9 * 2 * 9)必胜依次类推
CODE
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e6+7;
int main()
{
ios::sync_with_stdio(0);
int n;
while (cin>>n)
{
ll ans=1,num=0;
while(ans<n)
{
if(num%2==0)
{
ans*=9;
}
else
{
ans*=2;
}
num++;
}
if(num&1)
{
cout<<"Stan wins."<<endl;
}
else
{
cout<<"Ollie wins."<<endl;
}
}
return 0;
}
邂逅明下——HDU-2897
链接:https://acm.hdu.edu.cn/showproblem.php?pid=2897
大意
n枚硬币,每次最少取p枚,最多取q枚。取到最后一枚的输!
思路
巴什博弈拓展,只要把板子的(1+q)改成(p+q)即可(顺便加亿点细节)看代码
CODE
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e6+7;
int main()
{
ios::sync_with_stdio(0);
int n,p,q;
while (cin>>n>>p>>q)
{
if(n%(p+q)>p||n%(p+q)==0)//剩余的大于p,可以先手取到小于p,后面按(p+q)取即可
{
cout<<"WIN"<<endl;
}
else
{
cout<<"LOST"<<endl;
}
}
return 0;
}
A simple stone game——HDU-2486
链接:https://acm.hdu.edu.cn/showproblem.php?pid=2486
大意
一堆n个石头,先取者第一次可以取除全部取完任意多个石子,以后每次取不超过上次取子数的k倍,先取完者胜利。
思路
斐波拉契博弈拓展,k倍动态减法游戏。建一个类似于斐波拉契数列的数列,具体怎么建思路没太懂,先背板子了。数列内存在的数为必败态。
CODE
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e6+7;
int a[N],b[N];
int n,k,t,cas=0;
int main()
{
ios::sync_with_stdio(0);
cin>>t;
while(t--)
{
cin>>n>>k;
int i=0,j=0;
a[0]=b[0]=1;
while(a[i]<n)
{
i++;
a[i]=b[i-1]+1;
while(a[j+1]*k<a[i])
j++;
if(a[j]*k<a[i])
b[i]=b[j]+a[i];
else
b[i]=a[i];
}
cout<<"Case "<<++cas<<": ";
if(a[i]==n)
cout<<"lose"<<endl;
else
{
int ans;
while(n)
{
if(n>=a[i])
{
n-=a[i];
ans=a[i];
}
i--;
}
cout<<ans<<endl;//首发拿的数量
}
}
return 0;
}
Nim or not Nim?——HDU-3032
链接:https://acm.hdu.edu.cn/showproblem.php?pid=3032
大意
n堆石子,每次可以从任意一堆中取任意个石子,也可以将一堆石子分成两小堆,取走最后一个的胜利。
思路
尼姆博弈变形
需要用到一个SJ定理。
对于任意一个Anti-SG游戏,如果定义所有子游戏的SG值为0时游戏结束,先手必胜的条件:
1、游戏的SG值为0且所有子游戏SG值均不超过1
2、游戏的SG值不为0且至少一个子游戏SG值超过1
3、游戏的sg值就是nim的sg值。
每堆石子个数异或表示的什么?其实每堆石子个数就是这堆石子的SG值,所以本质上尼姆博奕是每堆石子的SG值异或运算;所以只需要求出现在的每堆石子SG值再进行异或运算;
SG的运算:SG[x]=mex(SG[y]|y是x的后续(通俗的讲,y是x可以一步到达的状态));mex()函数是求集合中未出现的最小非负整数;如mex(0,2,3)=1;
打表得到规律:
SG[4k+1]=4k+1;
SG[4k+2]=4k+2;
SG[4k+3]=4k+4;
SG[4k+4]=4k+3;
CODE
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e6+7;
int main()
{
ios::sync_with_stdio(0);
int t;
cin>>t;
while(t--)
{
int n;
cin>>n;
ll ans=0;
for(int i=1;i<=n;i++)
{
int x;
cin>>x;
if(x%4==0)
{
ans^=x-1;
}
else if(x%4==3)
{
ans^=x+1;
}
else
{
ans^=x;
}
}
if(!ans)
{
cout<<"Bob"<<endl;
}
else
{
cout<<"Alice"<<endl;
}
}
return 0;
}
Climbing the Hill——HDU-4315
链接:https://acm.hdu.edu.cn/showproblem.php?pid=4315
大意
在山上有n个人,每个人编号是1~n,这些位置只能同时被一个人占据,但是山顶可以同时被多个人占据,距离山顶第k近的是King,现在Alice和Bob开始向上送人,条件是不能跨越前面最近的人,问在Alice先手,双方最优的条件下谁能把King送到山顶。
思路
阶梯尼姆,头疼。。。之前写了个蓝桥杯一道阶梯尼姆题《高僧斗法》的博客,遇到这道题还是没做出来。
阶梯尼姆的主要思想为两两配对后求异或,这题为送国王到山顶的获胜。
所以我们考虑:
1、国王在第一位时,先手必胜。
2、国王在第二位时且n为奇数时,从后往前两两配对,那么第一个人最多只能停在a[1]上 , 因为如果第一个人爬到山顶,那么king就可以直接上山顶了,所以这题可以转化为 , 谁必须移动第一个人谁必败。所以需要减1。
3、其他情况,两两配对正常异或即可。
CODE
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e6+7;
int a[N];
int main()
{
ios::sync_with_stdio(0);
int n,m;
while(cin>>n>>m)
{
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
if(m==1)
{
cout<<"Alice"<<endl;
continue;
}
if(n&1)
{
int p=a[1];
if(m==2) p--;
for(int i=2;i+1<=n;i+=2)
{
p^=(a[i+1]-a[i]-1);
}
if(p)
{
cout<<"Alice"<<endl;
}
else
{
cout<<"Bob"<<endl;
}
}
else
{
int p=a[2]-a[1]-1;
for(int i=3;i+1<=n;i+=2)
{
p^=(a[i+1]-a[i]-1);
}
if(p)
{
cout<<"Alice"<<endl;
}
else
{
cout<<"Bob"<<endl;
}
}
}
return 0;
}