「Solution」Luogu P1225 黑白棋游戏

关于这道题目的解法,其他题解已经讲得非常清楚,另外,我在代码中也做了简单的注释,应该很容易就能理解题目的做法。

作为一道经典的位运算运用题目,我主要是想详细讲讲在这题中,位运算是如何使用的。

如果你没有接触过位运算,参考OI-Wiki

用了位运算才知道这东西有多爽

位运算的使用

这道题目中,bfs肯定需要进行判重,那么位运算就有很大的优势,因为棋盘布局一共只有\(2^{16}\)种可能,而棋盘中每一个点恰好对应0或1,于是容易想到将四排棋盘状态压成一行,然后将其当做二进制转为十进制,用0到65535这65536个数对应每种状态。

以下以样例为例,初始棋盘如下:

1111
0000
1110
0010

将其先压成一行:

1111000011100010

这就是棋盘的状态的二进制码,将其转为十进制,即61666。

为了方便进行位运算,我们为棋盘进行如下编号:

  0  1  2  3
  4  5  6  7
  8  9 10 11
 12 13 14 15

我们需要进行的操作其实就是两种:

  1. 获取编号为\(i\)棋子的颜色
  2. 交换编号为\(i\)和编号为\(j\)的棋子

对于操作1,用位运算表示为:(n >> (15 - i)) & 1

原理很简单,比如我们想获取样例中编号为3的棋子颜色,那么我们将3号棋子移动到第0位上,移动的位数即为15 - i位,所以就有了n >> (15 - i)

1111(000011100010)

接下来,将其和1进行与运算,其实就是获得了第0位的数码。

1111 & 0001 = 1

操作1就实现了,在程序中,15 - i被定义为了ni

对于操作2,用位运算表示为:n + (1 << (15 - j)) - (1 << (15 - i))

同样拿样例举例,我们想要将编号为1的棋子向下移动,即为将编号为1的棋子与编号为1 + 4 = 5的棋子互换。

为方便进行位运算,我们在程序中约定,被移动棋子只能是黑棋,目标棋子只能是白棋。

在二进制数当中,我们要做的就是将某一位上的1改成0,将某一位上的0改成1

很容易得到以下算式:

\[(1111000011100010)_2 - (100000000000000)_2 + (10000000000)_2 = (1011010011100010)_2 \]

转换成位运算就是n + (1 << (15 - j)) - (1 << (15 - i))

这样,我们就能在程序中使用位运算进行棋盘上棋子的移动操作了。

Tip

本题不能使用getchar!

本题不能使用getchar!

本题不能使用getchar!

RE记录

AC CODE

#include<bits/stdc++.h>
using namespace std;
struct node{
	int n; // 当前棋盘 
	int step; // 计步 
	string way; // 记录操作 
	node(int n, int step, string way): n(n), step(step), way(way) {}
};
int start, final, n, step, newn;
char t;
string way, newway;
bool vis[65536];
queue<node>q;
int main(){
	for(int i = 0; i < 4; i++)
		for(int j = 0; j < 4; j++)
			cin >> t, start = start * 2 + (int)(t-'0');
	for(int i = 0; i < 4; i++){
		for(int j = 0; j < 4; j++)
			cin >> t, final = final * 2 + (int)(t-'0');
		getchar();
	}
	q.push(node(start, 0, ""));
	vis[start] = true;
	while(!q.empty()){
		n = q.front().n, step = q.front().step, way = q.front().way;
		if(n == final){
			cout << step;
			for(int i = 0; i < (signed)way.size(); i++){
				if(i % 4 == 0) cout << endl;
				cout << way[i];
			}
			return 0;
		}
		step++;
		q.pop();
		for(int i = 0; i < 16; i++){
			int ni = 15 - i;
			if(((n >> ni) & 1) == 0) continue; // 被移动棋只选黑棋 
			// 左移 
			if(i % 4 != 0){ // 棋子不能位于棋盘第一列 
				int j = ni + 1;
				if(((n >> j) & 1) == 0){ // 目标棋应是白棋 
					newn = n + (1 << j) - (1 << ni);
					if(!vis[newn]){
						vis[newn] = true;
						newway = way + (char)(i/4+1+'0');
						newway += (char)(i%4+1+'0');
						newway += (char)((15-j)/4+1+'0');
						newway += (char)((15-j)%4+1+'0');
						q.push(node(newn, step, newway));
					}
				} 
			}
			// 右移 
			if(i % 4 != 3){ // 棋子不能位于棋盘第四列
				int j = ni - 1;
				if(((n >> j) & 1) == 0){
					newn = n + (1 << j) - (1 << ni);
					if(!vis[newn]){
						vis[newn] = true;
						newway = way + (char)(i/4+1+'0');
						newway += (char)(i%4+1+'0');
						newway += (char)((15-j)/4+1+'0');
						newway += (char)((15-j)%4+1+'0');
						q.push(node(newn, step, newway));
					}
				} 
			}
			// 上移 
			if(i >= 4){ // 棋子不能位于棋盘第一行 
				int j = ni + 4;
				if(((n >> j) & 1) == 0){
					newn = n + (1 << j) - (1 << ni);
					if(!vis[newn]){
						vis[newn] = true;
						newway = way + (char)(i/4+1+'0');
						newway += (char)(i%4+1+'0');
						newway += (char)((15-j)/4+1+'0');
						newway += (char)((15-j)%4+1+'0');
						q.push(node(newn, step, newway));
					}
				} 
			}
			// 下移
			if(i <= 11){ // 棋子不能位于棋盘第四行 
				int j = ni - 4;
				if(((n >> j) & 1) == 0){
					newn = n + (1 << j) - (1 << ni);
					if(!vis[newn]){
						vis[newn] = true;
						newway = way + (char)(i/4+1+'0');
						newway += (char)(i%4+1+'0');
						newway += (char)((15-j)/4+1+'0');
						newway += (char)((15-j)%4+1+'0');
						q.push(node(newn, step, newway));
					}
				} 
			} 
		}
	}
}
/*
1111
0000
1110
0010
1010
0101
1010
0101
*/

AC记录

  • 用时:59ms
  • 空间:1.00MB
posted @ 2020-10-08 14:54  Liuxizai  阅读(211)  评论(0编辑  收藏  举报