巴什博奕
若n%(m+1)==0,则后手赢,反之先手赢。
巴什博奕扩展
我们发现当石子数量为6的整数倍,无法被一次拿完,同时1~5都是能拿的
所以我们有结论,若n%6==0,则后手赢,反之先手赢。
#include<bits/stdc++.h>
using namespace std;
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(0);
int t;
cin>>t;
while(t--){
int n;
cin>>n;
if(n%6==0)cout<<"Roy wins!\n";
else cout<<"October wins!\n";
}
return 0;
}
Nim博弈
可以证明若当前异或值不为0,通过一次操作一定可以使其为0;若当前为零,无论如何操作,都会导致异或值不为0
当所有石头堆异或不为0,则先手赢
反Nim博弈
#include<bits/stdc++.h>
using namespace std;
int main() {
int t;
cin >> t;
while (t--) {
ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
int x = 0;
bool flag = 0;
while (n--) {
int y;
cin >> y;
if (y != 1)flag = 1;
x ^= y;
}
if (flag) {
if (!x)cout << "Brother\n";
else cout << "John\n";
} else {
if (!x)cout << "John\n";
else cout << "Brother\n";
}
}
return 0;
}
斐波那契博弈
首先有个结论,若n为斐波那契数,则先手必须全拿走才能赢,证明见左程云老师的视频
齐肯多夫(Zeckendorf)定理表示任何正整数都可以表示成若干个互不相邻的斐波那契数(不包括第一个斐波那契数)之和。
对于非斐波那契数n,拆分方式为找寻小于n的最大斐波那契数,n减去它,重复操作,得到拆分的斐波那契数,且先手至少拿走的数量为拆分出的斐波那契数的最小值
#include<bits/stdc++.h>
using namespace std;
#define N 1000000000000000
long long a[101];
int f(long long x, int r) {
int l = 1;
while (l + 1 < r) {
int mid = (r + l) >> 1;
if (a[mid] <= x)l = mid;
else r = mid;
}
return l;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
long long n;
cin >> n;
a[0] = 1, a[1] = 1;
int size=1;
while(a[size]<=n){
a[size+1]=a[size]+a[size-1];
size++;
}
int m = size-1;
if (a[m] == n) {
cout << n << "\n";
return 0;
}
n -= a[m];
int ans = a[m];
while (n > 0) {
m = f(n, m);
n -= a[m];
ans = a[m];
}
cout << ans << "\n";
return 0;
}
威佐夫博弈
SG函数和SG定理
举例:Nim博弈
#include <bits/stdc++.h>
using namespace std;
vector<int> a;
string nim(vector<int>& a){
int max1=0;
for(auto &num:a){
max1=max(max1,num);
}
int sg[max1+1];//记录sg值
sg[0]=0;
bool appear[max1+1];
for(int i=1;i<=max1;i++){
memset(appear,0,sizeof appear);//出现数组重置
//遍历后继节点
for(int j=0;j<i;j++){
appear[sg[j]]=true;
}
//搜寻未出现的最小自然数设为sg[i]
for(int s=0;s<=i;s++){
if(!appear[s]){
sg[i]=s;
break;
}
}
}
//sg定理
int eor=0;
for(int &num:a){
eor^=sg[num];
}
return eor!=0 ? "先手" : "后手";
}
两堆石头的巴什博奕
由之前的知识:sg[i]=i%(m+1)
因为sg[a]^sg[b]!=0为必胜态,所以若a%(m+1)!= b%(m+1),先手赢;
三堆石头拿斐波那契数博弈
不是很难。
E&D 游戏
通过sg找规律
#include <bits/stdc++.h>
using namespace std;
#define N 9
vector<int> a(4);
int sg[100][100];
void f() {
bool appear[10000];
for (int i = 2; i <= 100; i++) {
for (int j = 1; j <= i; j++) {
memset(appear, 0, sizeof appear);
for (int k = 1; k <= j / 2; k++) {
appear[sg[j - k][k]] = 1;
}
for (int k = 1; k <= i / 2; k++) {
appear[sg[i - k][k]] = 1;
}
for (int k = 0; k <= i; k++) {
if (!appear[k]) {
sg[i][j] = k;
break;
}
}
}
}
}
int main() {
sg[1][1] = 0;
f();
cout<<" ";
for(int i=1;i<=N;i++)cout<<(i)<<" ";
cout<<"\n\n\n";
for(int i=1;i<=N;i++){
cout<<(i)<<" ";
for(int j=1;j<i;j++){
cout<<"x ";
}
for(int j=i;j<=N;j++){
cout<<sg[j][i]<<" ";
}
cout<<'\n';
}
cout<<"将行列数减一"<<'\n';
cout<<" ";
for(int i=1;i<=N;i++)cout<<(i-1)<<" ";
cout<<"\n\n\n";
for(int i=1;i<=N;i++){
cout<<(i-1)<<" ";
for(int j=1;j<i;j++){
cout<<"x ";
}
for(int j=i;j<=N;j++){
cout<<sg[j][i]<<" ";
}
cout<<'\n';
}
return 0;
}
- 通过离谱的找规律发现sg[a][b]=(a-1)|(b-1)的二进制表示中最低位0所在的位置
从而得到最终代码
#include <bits/stdc++.h>
using namespace std;
//计算sg[a][b]
int f(long long a,long long b){
int cnt=0;
long long x=(a-1)|(b-1);
while(x&1){
cnt++;
x>>=1;
}
return cnt;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int t,n;
long long a,b;
cin>>t;
while(t--){
cin>>n;
int eor=0;
for(int i=0;i<n/2;i++){
cin>>a>>b;
eor^=f(a,b);
}
if(eor)cout<<"YES\n";
else cout<<"NO\n";
}
return 0;
}
分裂游戏
- 将每个糖豆看成独立的游戏,设其所在的位置为i,则该游戏为sg[i];
- 考虑sg[i]的求解方法,遍历所有可能分解情况,例如分解到j,k位置,则考虑sg[j]^sg[k],sg[i]不能等于上述值。最后找到最小的可选自然数。
#include <bits/stdc++.h>
using namespace std;
int sg[23];
bool appear[400];
int main(){
//sg函数,将瓶子编号倒一下,方便处理
sg[0]=0;
sg[1]=1;
for(int i=2;i<23;i++){
memset(appear,0,sizeof appear);
appear[0]=1;
for(int j=i-1;j>=0;j--){
for(int k=j-1;k>=0;k--){
appear[sg[j]^sg[k]]=1;
}
}
for(int m=0;m<400;m++){
if(!appear[m]){
sg[i]=m;
break;
}
}
}
int t,n,x;
cin>>t;
while(t--){
cin>>n;
//sg定理
int eor=0;
for(int i=0;i<n;i++){
cin>>x;
if(x&1)eor^=sg[n-1-i];
}
if(eor==0){
cout<<"-1 -1 -1\n0\n";
}else{
//求第一步的方案数
int cnt=0;
int flag=1;
for(int i=n-1;i>=0;i--){
for(int j=i-1;j>=0;j--){
for(int k=j;k>=0;k--){
if((eor^sg[i]^sg[j]^sg[k])==0)cnt++;//将sg[i]去掉,将sg[j]和sg[k]加上,eor就变成将他们异或
if(cnt==1 && flag)cout<<(n-1-i)<<' '<<(n-1-j)<<' '<<(n-1-k)<<'\n',flag=0;
}
}
}
cout<<cnt<<'\n';
}
}
return 0;
}
妙不可言妙不可言~~~~