涉及到的理论
思路:被分成两个相同的组的状态是必败态
作出对称的状态后再模仿对手
如果个数不大于2,Alice能一次拿完,否则Alice每拿一次,Bob能通过取走一枚或两枚硬币将其分成两个长度相同的链,则留给Alice的是必败态。
#include
using namespace std;
int main()
{
int n;
while(cin>>n)
{
if(!n) break;
if(n<=2) cout<<"Alice"<
https://www.luogu.org/blog/zyy/solution-p1290 这个题解很好的鸭
#include
#include
using namespace std;
int a,b;
void solve()
{
bool f=true; //f为先手是否必胜
for(;;)
{
if(a>b) swap(a,b);
if(b%a==0) break; //出现了一个必胜态
if(b>2*a) break; //另一个必胜态
b-=a;
f=!f;
}
if(f) cout<<"Stan wins"<>n;
for(int i=1;i<=n;++i)
{
cin>>a>>b;
solve();
}
return 0;
}
思路:将棋子两两配对,如果为奇数,就在p=0的位置添加一个棋子
将棋子之间的空格数看作一堆石子,根据Nim游戏的策略,如果石子数的异或值!=0,先手必胜
#include
#include
using namespace std;
const int N=1e3+10;
int p[N];
int main()
{
int t;
cin>>t;
while(t--)
{
int n;
cin>>n;
for(int i=1;i<=n;++i)
cin>>p[i];
if(n%2) p[++n]=0;
//cout<
poj 2960 S-Nim SG函数
题意:每个测试用例第一行给出一个集合S,每次得取S里面的数个珠子,第二行给出游戏个数m,接下来m行给出分别给出每次游戏中堆的个数以及每堆有多少个珠子,如果先手胜输出W,先手败输出L。每行样例后输出一新行。
思路:和硬币游戏2基本一样,就是在Nim上加了一个条件,每次得选给定集合里的数个硬币
n堆石子的Nim游戏本身就是n个“任取石子游戏”的和,每个任取石子游戏的SG值为石子个数(考虑定义SG函数值是第一个转移不到的整数),所以Nim的话就是所有石子数异或
Nim中有x颗石子的石子堆,能转移成有0,1,...,x-1颗石子的石子堆
从Grundy值为x的状态出发,可以转移到Grundy值为0,1,...x-1的状态。
注意这道题如果每种position都算一次的话会T
对于给定的每次可取硬币个数的集合,Grundy值就是定值,因此可以提前打表算好,或者用记忆化搜索
下为打表版本
#include
#include
#include
#include
#include
using namespace std;
const int N=10100;
int s[110];
int heap[110];
int grundy[10100];
bool vis[10100];
int maxx=0;
int k;
int n;
void pre()
{
memset(grundy,-1,sizeof(grundy)); //对于每个s,grundy都是相同的,不需要重复计算
grundy[0]=0;
for(int i=1;i=s[j])
{
int tmp=i-s[j];
vis[grundy[tmp]]=1;
}
}
for(int j=0;j>k)
{
if(!k) break;
for(int i=1;i<=k;++i)
cin>>s[i];
pre();
int m; //m种情况
cin>>m;
while(m--)
{
cin>>n;
int x=0;
for(int i=1;i<=n;++i)
{
cin>>heap[i]; //每堆有多少个珠子
x^=grundy[heap[i]];
}
if(x) cout<<'W';
else cout<<'L';
}
cout<
记忆化搜索(T)用set会T啊啊啊
#include
#include
#include
#include
#include
using namespace std;
int s[110];
int heap[110];
int grundy[10100];
int maxx=0;
int k;
int n;
int solve(int x) //一个游戏的解 记忆化搜索
{
if(grundy[x]!=-1) return grundy[x];
setss;
for(int i=1;i<=k;++i)
{
if(s[i]<=x) ss.insert(solve(x-s[i]));
}
int g=0;
while(ss.count(g)!=0) g++;
grundy[x]=g;
return grundy[x];
}
int main()
{
while(scanf("%d",&k)!=EOF)
{
if(!k) break;
for(int i=1;i<=k;++i)
scanf("%d",&s[i]);
memset(grundy,-1,sizeof(grundy)); //给定一组s,grundy是确定的
grundy[0]=0;
int t;
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
int x=0;
for(int i=1;i<=n;++i)
{
scanf("%d",&heap[i]);
x^=solve(heap[i]);
}
if(x) cout<<'W';
else cout<<'L';
}
cout<
然后就换种写法
我喵喵喵?气的昏古去,bool vis[]数组开在外面了。。。回退的时候修改了当前状态的vis数组
awsl
#include
#include
#include
#include
#include
using namespace std;
int s[110];
int heap[110];
int grundy[10100];
//int sg[10010];
int maxx=0;
int k;
int n;
int solve(int x)
{
if(grundy[x]!=-1) return grundy[x];
int vis[10100]; //嗯嗯嗯?
memset(vis,0,sizeof(vis));
for(int i=1;i<=k;++i)
{
if(s[i]<=x)
{
vis[solve(x-s[i])]=1;
}
}
for(int i=0;i<10100;++i)
{
if(!vis[i])
return grundy[x]=i;
}
}
int main()
{
while(cin>>k)
{
memset(grundy,-1,sizeof(grundy)); //对于每个s,grundy都是相同的,不需要重复计算
grundy[0]=0;
if(!k) break;
for(int i=1;i<=k;++i)
cin>>s[i];
int m; //m种情况
cin>>m;
while(m--)
{
cin>>n;
int x=0;
for(int i=1;i<=n;++i)
{
cin>>heap[i]; //每堆有多少个珠子
x^=solve(heap[i]);
}
if(x) cout<<'W';
else cout<<'L';
}
cout<
思路:首先感受一下这个游戏符合ICG
1、有两名选手
2、两名选手交替对游戏进行移动(move),每次一步,选手可以在(一般而言)有限的合法移动集合中任选一种进行移动;
3、对于游戏的任何一种可能的局面,合法的移动集合只取决于这个局面本身,不取决于轮到哪名选手操作、以前的任何操作、其它什么因素;
4、如果轮到某名选手移动,且这个局面的合法的移动集合为空(也就是说此时无法进行移动),则这名选手负。
以下摘自《挑战》,会发生分割的游戏,也能计算Grundy值,当w*h的纸张分为两张时,假设所分得的纸张的Grundy值分别为g1,g2,则这两张纸对应的状态的Grundy值可以表示为g1^g2
枚举所有一步能转移到的状态的Grundy值,来计算Grundy值
记忆化搜索
#include
#include
#include
#include
using namespace std;
const int N=210;
int mem[N][N];
int sg(int w,int h)
{
if(mem[w][h]!=-1) return mem[w][h];
sets;
for(int i=2;w-i>=2;++i) s.insert(sg(w-i,h)^sg(i,h));
for(int i=2;h-i>=2;++i) s.insert(sg(w,h-i)^sg(w,i));
int g=0;
while(s.count(g)) g++;
return mem[w][h]=g;
}
int main()
{
int w,h;
memset(mem,-1,sizeof(mem));
while(scanf("%d%d",&w,&h)!=EOF)
{
if(sg(w,h)!=0) cout<<"WIN"<
关于Grundy值的上限,只要考虑一张纸最多能转移的状态数,最多能转移200次
int sg(int w,int h)
{
if(mem[w][h]!=-1) return mem[w][h];
bool vis[210]; 210就A了
memset(vis,0,sizeof(vis));
for(int i=2;w-i>=2;++i)
{
int tmp=sg(w-i,h)^sg(i,h);
vis[tmp]=1;
}
for(int i=2;h-i>=2;++i)
{
int tmp=sg(w,h-i)^sg(w,i);
vis[tmp]=1;
}
for(int i=0;;++i)
if(!vis[i])
return mem[w][h]=i;
}