NEFU OJ Problem1356 帽儿山奇怪的棋盘 题解
题目:
Time Limit:1000ms | Memory Limit:65535K
Description
军哥来到了帽儿山,发现有两位神人在顶上对弈。棋盘长成下图的模样:
![]()
每个点都有一个编号:由上到下,由左到右,依次编号为 1、2……12。两位神人轮流博弈,每一轮操作的一方可以取走一个棋子,或者取走相邻的两个棋子(即在同一直线上相邻的棋子)。取走最后一颗棋子的人输。给定初始状态,如果两个人都采取最优决策,问谁能赢。
Input
第一行一个数 n 表示数据组数,接下来 n 行每行一个长度为 12 的 01 串,1 表示该位置上有棋子,0 表示没有。(n<=100)
Output
输出一行,一个长度为 n 的 01 串,0 表示先手赢,1 表示后手赢。
Sample Input
3
110000000000
100000000000
110000000011
Sample Output
010
思路:
利用博弈论思想:
当某状态无论怎么取均变为必胜态,该态为必败态。
当某状态可以转为必败态,该态为必胜态。
·先考虑棋盘状态表示:考虑到棋盘较小, 直接用12位二进制数\(p_0p_1p_2……p_{11}\) 表示棋盘状态。其中\(p_i=1\)表示第i位有棋子。
·再考虑拿法,共分为两种:
- 拿一个棋子:将位置\(i\)对应位\(p_{i-1}\)置0(\(p_{i-1}\)需为1)
- 拿相邻两个棋子:如果直接判断较为复杂,因为棋盘较小,直接用12位数组预存相邻的12种棋子情况。
由以上思路暴力跑一遍所有情况(情况数小于8192)储存在game数组中,输入数据时直接调用对应胜负情况即可。
个人代码如下供大家参考:
#include <bits/stdc++.h>
using namespace std;
const int N = 1<<13;//最大棋盘数值
int game[N];//某棋盘对应胜负情况
int posConnect[12][2] = {
{1,4},
{3,4},
{2,5},
{5,6},
{7,8},
{8,11},
{9,12},
{9,10},
{4,5},
{4,8},
{8,9},
{5,9}
};//所有相连位置
int Dp(int x)//返回1代表胜,0代表败
{
if(game[x] != -1)
return game[x];
//找必败态
for(int i = 0; i < 12; i ++)//查找取单子情况
{
int pos = 1 << i;
if(pos & x && !Dp(x-pos))//某位置是1,且去掉后为必败态
return game[x] = 1;
}
for(int i = 0; i < 12; i ++)//查找取连子情况
{
int pos1 = 1 << posConnect[i][0]-1; //位置转二进制
int pos2 = 1 << posConnect[i][1]-1; //位置转二进制
if(pos1 & x && pos2 & x && !Dp(x-pos1-pos2))//相邻两位置都为1,且去掉后为必败态
return game[x] = 1;
}
//没有找到必胜态,必败
return game[x] = 0;
}
int Func(string s)//处理输入, 字符串转二进制数
{
int res = 0;
for (int i = 0; i < 12; i ++)
if(s[11-i] == '1')
res += 1 << i;
return res;
}
int main()
{
queue<bool> res; // 用队列存每次输入的结果
memset(game,-1,sizeof game);
game[0] = 1;
int n;
cin >> n;
while(n--)
{
string str;
cin >> str;
int x = Func(str);
res.push(!Dp(x));//存入结果队列
}
while(!res.empty())//输出所有结果
{
cout << res.front();
res.pop();
}
return 0;
}
总结:
·当情况较少时可以使用暴力dp计算博弈论。
·用二进制数存棋盘局面可以较为方便的实现棋盘存储和运算。其中遇到相连棋子等判断问题,可以先计算预存所有该情况(情况较少时可手动写入)。

浙公网安备 33010602011771号