Time Limit: 3 second
Memory Limit: 2 MB
【问题描述】
黑白棋游戏的棋盘由4*4方格阵列构成。棋盘的每一方格中放有1枚棋子,共有8枚白棋子和8枚黑棋子。这16枚棋子的每一种放置方案都构成一个游戏状态。在棋盘上拥有1条公共边的2个方格称为相邻方格。一个方格最多可有4个相邻方格。在玩黑白棋游戏时,每一步可将任何2个相邻方格中棋子互换位置。对于给定的初始游戏状态和目标游戏状态,编程计算从初始游戏状态变化到目标游戏状态的最短着棋序列。【输入格式】
共有8行。前四行是初始游戏状态,后四行是目标游戏状态。每行4个数分别表示该行放置的棋子颜色,“0”表示白棋,“1”表示黑棋。
【输出格式】
第一行是着棋步数n,接下来n行,每行4个数分别表示该步交换棋子的两个相邻方格的位置。例如abcd表示将棋盘上(a,b)处的棋子与(c,d)处的棋子换位。
【输入样例】
1111 0000 1110 0010 1010 0101 1010 0101
【输出样例】
4 1222 1424 3242 4344
【题解】
这题的难点在状态的判重上。需要用到二进制来表示棋子的状态。
首先,预处理出2^1->2^16;存在一个数组中。需要的时候直接访问即可。
然后把4行的棋子状态转变为一行。
如输入样例 起始状态为 1111000011100010;一共16个字符。
注意一开始的时候在字符串前加个空格。这样这串字符就从字符串的下标1开始了。
然后for (int i = 1;i <= 16;i++) sum +=two_n[i]*(s[i]-'0');
这样就把“二进制转化成10进制了”但注意真正的二进制转10进制需要逆序 且是从2^0开始的。
但我们只需要用二进制的思想存储状态就可以了。
然后bo[sum] = true,标记这个状态已经出现过。
然后进行广搜。
在广搜的时候不要把这段“十进制”转化成字符串。而是直接在这个“十进制”上进行操作。
我们在搜的时候只要考虑两个变换方向即向右和向下。因为向左和向右会重复。
同时我们要判断两个棋子颜色是否不同,如果颜色相同则移动毫无意义。
然后for(int i = 1;i <= 16;i++)
{ if ( i不是4的倍数)就可以向右移动
x = i+4;
如果x <=16就可以向下移动。其中x和i对应了需要移动的棋子 }
然后是变换的原理
比如
11100000000
我们要把第4个0变成1,只要加上2^4就可以了。
我们要把第2个1变成0,只要减去2^2就可以了。
具体的操作请看代码
【代码】
#include <cstdio>
#include <string>
#include <iostream>
using namespace std;
const int maxdl = 100000;
struct ss
{
int x1,y1,x2,y2;
};
int two_n[17],f,t,team[maxdl],step[maxdl],pre[maxdl];
string s1,s2;
bool panchong[131079*2] = {0};
ss d_step[maxdl];
void input_data(string & s1)
{
s1 =" ";
string ss;
for (int i = 1;i <= 4;i++)
{
getline(cin,ss);
s1+=ss;
}
}
void init() //先预处理出2的1次方到2的16次方
{
two_n[0] = 1;
for (int i = 1;i <= 16;i++)
two_n[i] = two_n[i-1]*2;
for (int i = 1;i <= 16;i++) //把“二进制转换成10进制 "
f += (s1[i]-'0')*two_n[i];
for (int i = 1;i <= 16;i++)
t += (s2[i]-'0')*two_n[i];
}
void output_ans(int temp) //用于递归输出方案。
{
if (temp == 1) //如果前一个是头指针则结束。这是递归终点
return;
output_ans(pre[temp]);
printf("%d%d%d%d\n",d_step[temp].x1,d_step[temp].y1,d_step[temp].x2,d_step[temp].y2);
}
void get_ans()
{
int head = 0,tail =1;
team[1] = f; //team数组用于存储以“十进制”存储的状态
step[1] = 0;
if (f == t) //如果初始状态和末状态相同,则直接输出0
{
printf("0");
return;
}
panchong[f] = true; //标记这个状态已经找过
while (head != tail)
{
head++;
int ff = team[head],ss = step[head]; //取出状态和步骤数
bool bo[17] = {0}; //用于“把十进制换成二进制”
for (int i = 1;i <= 16;i++)
if ( (ff & two_n[i]) > 0) //如果之前存过2^i次方,在这个伪10进制转换成2进制后
//倒数第i+1位确实会变成1.所以和2^i进行与操作,如果这个位置上有棋子,最后结果会是大于0的。
//比如有0011这样的情况。第3,4位为1,我们转换成的伪十进制是24;
//之后我们要把这个棋子信息存进bo数组中
//我们枚举i,i到了3,用2^3和24进行与运算
//2^3的二进制是1000
//24的二进制是11000 ,可以看到第4位都是1,所以我们判断i=3时,bo[i] = 1(true);
//再枚举到4时
//2^4的二进制是10000
//24的二进制是11000 ,可以看到第5位都是1,所以我们判断i=4时,bo[i] = 1(true);
//这种虽然和二进制转10进制的正确做法不同,但可以用这种“错误”的方法来存储状态
//并且在用位运算进行“压缩”和“解压”
//还有变换的例子。
//还是上面的0011
//伪十进制为24;
//如果我们想把第3个1变成0就减去2^3.
//就变成了16;
//再用伪方法转换成二进制。就变成0001了。
bo[i] = true;
for (int i = 1;i <= 16;i++)
{
int temp,x;
x= i + 4; //优先往下转换
if (x <= 16)
{
if (bo[i] == (1-bo[x])) //如果那个位置的棋子颜色和当前枚举到的棋子颜色不同
{
temp = ff; //対temp进行操作,不影响原数字
if (bo[x] == 0) //用加减法分别把0变成1,1变成0(二进制上的1/0)
temp += two_n[x];
else
temp-=two_n[x];
if (bo[i] == 0)
temp +=two_n[i];
else
temp-=two_n[i];
if (!panchong[temp]) //变换完之后进行判重
{
panchong[temp] = true;
tail++;
if ((i % 4) != 0) //这里要把一维坐标转换成二维的,注意几个能被4整除的数的判断即可
d_step[tail].x1 = (i/4) + 1;
else
d_step[tail].x1 = i/4;
d_step[tail].y1 = ((i-1) % 4) + 1;
if ((x % 4)!=0)
d_step[tail].x2 = (x/4) + 1;
else
d_step[tail].x2 = x / 4;
d_step[tail].y2 = ((x-1) % 4) + 1;
team[tail] = temp; //记录这个状态
step[tail] = ss+1;
pre[tail] = head; //记录前一个状态是什么 (最后输出方案)
if (temp == t)
{
printf("%d\n",ss+1);
temp = tail;
output_ans(temp); //用递归输出答案
return;
}
}
}
}
if ( (i % 4) !=0) //如果不是最右边的数字
{
x = i+1;
if (bo[x] == (1-bo[i])) //如果颜色和当前的不同
{
temp = ff;
if (bo[x] == 0) //在temp上进行操作
temp += two_n[x];
else
temp-=two_n[x];
if (bo[i] == 0)
temp +=two_n[i];
else
temp-=two_n[i];
if (!panchong[temp]) //如果之前没有找到过这个状态
{
panchong[temp] = true;//进行判重
tail++; //把这个步骤加入到队列当中
if ( (i%4) !=0) //把一维坐标转换成二维坐标
d_step[tail].x1 = (i/4) + 1;
else
d_step[tail].x1 = i/4;
d_step[tail].y1 = ((i-1) % 4) + 1;
if ((x % 4) != 0)
d_step[tail].x2 = (x/4) + 1;
else
d_step[tail].x2 = x /4;
d_step[tail].y2 = ((x-1) % 4) + 1;
team[tail] = temp;
step[tail] = ss+1; //步骤数+1
pre[tail] = head;
if (temp == t) //如果找到了目标状态 则停止。
{
printf("%d\n",ss+1);
temp =tail;
output_ans(temp);
return;
}
}
}
}
}
}
}
int main()
{
//freopen("F:\\rush.txt","r",stdin);
input_data(s1);
input_data(s2);
init();
get_ans();
return 0;
}