Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others) |
藉由此题来学习sg(Sprague-Grundy)函数和nim游戏。
题意:有若干堆石子,两人凭借自己火热的取胜心(的意思就是这两个人在比试中不回失误)从堆中取石子,遵循以下游戏规则:
1、每次可一丛一堆石子中取任意数量的石子,但每次只能对一堆进行操作
2、也可以选择不取,将一堆石子分成三堆,三堆石子的数量自己决定
3、取走最后石子的人胜
这个游戏叫做nim游戏,似乎是博弈论中很经典的一个游戏。若想做出这道题,首先要了解sg函数是什么,下面谈谈我对sg函数的理解:
给定一个有向无环图和一个起始顶点上的一枚棋子,两名选手交替的将这枚棋子沿有向边进行移动,无法移动者判负。sg函数就能描述这个状态
结合本题来讲,当sg函数等于0时,就说明没有下一步操作了,则此时选手状态输。那么怎么计算sg函数的值呢?当只有一堆石子,我们不对它进行分堆操作的时候,它的sg函数就等于它自身的值。
但题目没有那么简单,还能进行分堆操作,对于多堆的石子的sg函数,等于各个堆的sg值作 ^ 运算(这啥玩意),那各个堆的sg函数怎么算呢,。。就是sg(x) =mex{sg(y) : y ∈ F(x)},意思是在非负整数集中将x的所有后继点的sg值全部去掉,然后集合里还剩下的最小非负整数就是x的sg值,比如某数的所有后继点的sg值为{1,2,4},那么这个数的sg值就为0
题目中给的数非常大,直接对每个输入求sg值似乎是不可能的,所以要根据这个游戏的规则打一打每个数sg值表,看看有没有规律可循。那么开撸sg函数吧,最开始我们只知道0、1的sg值,但很快就可以递推出所有的了!
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <cmath>
#include <cstdlib>
#include <ctime>
#include <stack>
using namespace std;
const int maxn = 105;
int sg[maxn];
bool vis[maxn];
int main(){
int i,j,k;
sg[0]=0;
for(i=1;i<=100;i++){
memset(vis, 0, sizeof(vis));
for(j=0;j<i;j++){
vis[sg[j]]=true;
}
for(j=1;j<i;j++){
for(k=1;k<i-j;k++){
vis[sg[j]^sg[k]^sg[i-k-j]]=true;
}
}
for(j=1;vis[j];j++);
sg[i]=j;
}
for(i=0;i<=100;i++){
cout<<i<<" : "<<sg[i]<<endl;
}
return 0;
}
打了个0~100的sg值
根据这个sg值表,我们可以发现8的倍数的数的sg值和他前一位数的sg值是交换的,既然找到规律题目就好做了。将题目输入的多个堆sg的值作 ^ 运算就行了
下面代码:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <cmath>
#include <cstdlib>
#include <ctime>
#include <stack>
using namespace std;
const int maxn = 1e6;
int save[maxn+5];
int main(){
int t;
scanf("%d",&t);
while(t--){
int n,i;
scanf("%d",&n);
for(i=1;i<=n;i++){
scanf("%d",&save[i]);
if(save[i]%8==0){
save[i] -= 1;
}else if((save[i]+1)%8==0){
save[i] += 1;
}
}
int judge=save[1];
for(i=2;i<=n;i++){
judge=judge^save[i];
}
if(judge==0){
printf("Second player wins.\n");
}else{
printf("First player wins.\n");
}
}
return 0;
}
需要注意的是判断一个数要判断它是不是8的倍数再判断它加一是不是8的倍数,这两个属性只能取其一