【9801】黑白棋游戏
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; }