题目描述
AKN玩游戏玩累了,于是他开始和同伴下棋了,玩的是跳棋!对手是wwx!这两位上古神遇在一起下棋,使得棋局变得玄幻莫测,高手过招,必有一赢,他们都将用最佳策略下棋,现在给你一个n*20的棋盘,以及棋盘上有若干个棋子,问谁赢?akn先手!
游戏规则是这样的:
对于一个棋子,能将它向右移动一格,如果右边有棋子,则向右跳到第一个空格,如果右边没有空格,则不能移动这个棋子,如果所有棋子都不能移动,那么将输掉这场比赛。
输入格式:
第一行一个T,表示T组数据
每组数据第一行n,表示n*20的棋盘
接下来n行每行第一个数m表示第i行有m个棋子
随后跟着m个数pj表示第i行的棋子布局
输出格式:
如果AKN能赢,则输出”YES”,否则输出”NO”
输入输出样例
输入样例
2
1
2 19 20
2
1 19
1 18
输出样例
NO
YES
说明
10%的数据T≤1,n≤1
另外10%的数据m≤1
100%的数据T≤100,n≤1000,m≤20,1≤pj≤20
.
.
.
.
.
题解:
转载自dalao博客:https://www.luogu.org/blog/xmy/solution-p2575
这里讲讲怎么实现求sg函数的过程。
我们先考虑一行的棋盘。
假设以x为棋盘的状态,二进制中0为该位无棋子,1为有棋子。
首先,如果棋子都聚集在棋盘最右边,则任何一个棋子都不能动,所以sg[x]=0。
设x=(00…00111..11)2
其中有a个1,则x=2^0+2^1+…+2^(a-1)=2^a-1
这些状态我们可以预处理出。
然后,若x不是这个状态,则要删去右边的不可以移动的棋子。
若x=(100101011…111..11)2,前面部分是随意的,后面部分都是1,那最后的连续的1都不是可以移动的棋子
把x加1得到(100101011…1000..00)2,只要去掉最后一个1就可以了
所以x等价于(x+1)-lb(x+1),其中lb是求x最后一个1的状态的函数
最后的才是重点:
每次找最右边的没有计算过的一颗棋子,求出它的后继状态,这很容易。设t=x,每计算一次最右边的棋子,t中就减去这个棋子。设k=lb(t),从左到右枚举k,找到第一个空位并把原来的棋子放在这个位置上就行。
#include<bits/stdc++.h>
using namespace std;
int sg[1048576],i,T,n,m,ans,s,x,a[21],j;
int lb(int x){
return x&(-x);
}
int dfs(int x){
if (sg[x]!=-1) return sg[x];
int t=(x+1)-lb(x+1),k=lb(t),cnt=0,tmp;
while (k){//边界条件是棋盘上还有可以移动且没被计算过的棋子
t^=k;tmp=k;
for (;(x^k)<x;k>>=1);//找没有棋子的位置
a[cnt++]=dfs(x^k^tmp);//异或tmp是拿掉原来位置上的棋子相当于减,异或k是放到空位上,相当于加
k=lb(t);
}
sort(a,a+cnt);
if (a[0]) return sg[x]=0;
for (int i=1;i<cnt;i++)
if (a[i]-a[i-1]>1) return sg[x]=a[i-1]+1;
return sg[x]=a[cnt-1]+1;
}
int main(){
memset(sg,-1,sizeof(sg));
for (i=0;i<=20;i++) sg[(1<<i)-1]=0;
for (i=0;i<(1<<20);i++)
if (sg[i]==-1) sg[i]=dfs(i);
cin>>T;
while (T--){
scanf("%d",&n);
ans=0;
for (i=0;i<n;i++){
scanf("%d",&m);
s=0;
for (j=0;j<m;j++) scanf("%d",&x),s|=(1<<20-x);
ans^=sg[s];
}
printf("%s\n",ans?"YES":"NO");
}
}
本文介绍了一种通过计算SG函数来预测二人跳棋游戏胜负的方法。游戏在一个n*20的棋盘上进行,玩家轮流移动棋子。当一方无法移动任一棋子时则判负。文章详细阐述了如何利用SG函数评估棋盘状态,并提供了具体的实现代码。

242

被折叠的 条评论
为什么被折叠?



