题目描述
一个游戏的规则是: 共有 n 堆石子, 标号为 0,1,2…..n-1, 第 i堆石子中有p[i]个石子,两个人轮流取石子,每一轮每人选择 3 堆石子。标号为 i,j,k, 并要保证 i < j , j < = k 且第 i堆中至少要有 1 个石头,随后这个人从第 i 堆中拿走一个石子,并在 j,k中各放入一个石子(j 可能等于 k) 。
如果轮到某人而他无法按规则取石子,那么他将输掉比赛。1 < n < = 21,p[i] < = 10000 先手想要知道是否能赢下比赛,若能赢下,他希望知道第一次去石子的方案及方案数。
输入
输入文件第一行是一个整数t表示测试数据的组数,接下来为t组测试数据(t<=10)。
每组测试数据的第一行是石子堆的个数n,
接下来的一行有n个由空格隔开的非负整数,表示每堆石子的石子个数。
输出
对于每组测试数据,输出包括两行.
第一行:如无法赢得游戏,那么输出用一个空格两两隔开的三个-1,否则输出字典序最小的i,j,k.
第二行:输出方案数
样例输入
2
4
1 0 1 5000
3
0 0 1
样例输出
0 2 3
1
-1 -1 -1
0
想法
- 经典的sg题目(sg处女题)
- 定义每个子游戏为一个石子所在位置
算法
- 定义sg(i)为 一个石子在位置i的sg值
- 由于数据范围允许 sg(i)可根据定义暴力求出
代码
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <string>
#include <cmath>
using namespace std;
int T,n,a[25],sg[25],ans,tot;
bool flag[16385];
inline void pre()
{
sg[1]=0;
for (int i=2;i<=24;i++)
{
memset(flag,0,sizeof(flag));
for (int j=1;j<i;j++)
for (int k=1;k<=j;k++)
flag[sg[k]^sg[j]]=true;
for (int j=0;j<=16385;j++)
if(flag[j]==false){sg[i]=j;break;}
}
}
int main()
{
//freopen("game.in","r",stdin);
//freopen("game.out","w",stdout);
scanf("%d",&T);
pre();
while(T--)
{
ans=0,tot=0;
scanf("%d",&n);
for (int i=1;i<=n;i++)
scanf("%d",&a[i]);
for (int i=1;i<=n;i++)
if(a[i]&1)ans^=sg[n+1-i];
for (int i=1;i<=n;i++)
for (int j=i+1;j<=n;j++)
for (int k=j;k<=n;k++)
if((ans^sg[n+1-i]^sg[n+1-j]^sg[n+1-k])==0)
{
tot++;
if(tot==1)printf("%d %d %d\n",i-1,j-1,k-1);
}
if(!tot)printf("-1 -1 -1\n");
printf("%d\n",tot);
}
return 0;
}