POJ1753解题报告(位压缩+DFS/BFS)

这篇博客探讨了POJ1753问题的解决方案,通过位压缩技术节省空间,同时使用DFS和BFS进行遍历。作者指出,每个位置最多只需翻转一次,因此可以遍历所有2^16种状态。尽管BFS在找到最短路径后会停止,但DFS会遍历所有可能,只需保存最短路径。文章还提供了Python脚本来表示位翻转的异或操作,并分享了一段BFS的实现代码,强调在BFS中不应修改已访问状态的步长,以保持算法的正确性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述:
Flip game is played on a rectangular 4x4 field with two-sided pieces placed on each of its 16 squares.
One side of each piece is white and the other one is black and each piece is lying either it's black or white side up.
Each round you flip 3 to 5 pieces, thus changing the color of their upper side from black to white and vice versa.
The pieces to be flipped are chosen every round according to the following rules: 
Choose any one of the 16 pieces. 
Flip the chosen piece and also all adjacent pieces to the left, to the right, to the top, and to the bottom of the chosen piece (if there are any).

其实就是翻转每个位置,有些文章里面说到,每个位置只需要至多被翻转一次,这个是理论依据。毕竟一个位置变偶数次那么这个位置就复原了。

基本的思想应该是遍历2^16个状态,用int显然太浪费空间了,所以考虑位压缩。遍历其实有两种方式一种是BFS这是最基本的,还有简洁一点的就是DFS,都是遍历,BFS找到了最短路劲就退出,但是DFS不会,必须遍历完,但是只要保存下来最短的一次就好了,但是代码很简洁。

位翻转的思想是16位排成[15 14 13...3 2 1 0]这样的15位。

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

翻转0位置显然是翻转0 1 4这样 需要与 0x13异或 不推荐有些人的加法,明显按位异或才是对的,加法还要去模不太建议

对于每个位翻转应该异或的数,我用Python脚本表示出来了

def my(l):
	ans = 0
	for i in l:
		ans += ( 2 ** i)
	return ans

l为list对象输入my([0,1,4]) ....一直到my([11,14,15])就得到了一个数组,翻转哪一位的时候异或就好了,下面是全部的代码:

#include <iostream>
#include<fstream>
#include<cstdlib>
#include<cstring>
#include<cstdio>
using namespace std;
const int MAX = 16;
unsigned int current_state = 0;
int final_step = MAX;
const unsigned int flips[] = {19,39,78,140,305,626,1252,2248,
                                4880,10016,20032,35968,12544,29184,58368,51200};
void init(char str[]){
	final_step = MAX;
	current_state = 0;
	for(int i=0;i<MAX;i++){
		if(str[i] == 'b'){
			current_state += (1 << i);
		}
	}
}

int flip(unsigned int state,int index){
	return (state ^ flips[index]);	
}

void mysearch(unsigned int state,int current_index,int step)
{
	//if current_index > 15 应该置于后面 
	if(state == 0 || state == 65535){
		if(step < final_step)
			final_step = step;
		return;
	}
	if(current_index > 15) return;
	mysearch(state,current_index + 1,step);
	state = flip(state,current_index);
	mysearch(state,current_index + 1,step + 1);
	return;
}

int main(int argc, char** argv) {	

	char str[MAX];
	int number = 0;
	char c;
	while(cin >> c)
	{
		if(c==' ' || c == '\n')
		 continue;
		 str[number++] = c;
		 if(number == 16) break;
	}
	init(str);
	mysearch(current_state,0,0);
	if(final_step == MAX)
		cout << "Impossible";
	else
		cout << final_step;
	cout << endl;
	return 0;
}


其中开始遇到了一些问题,比如
if current_index > 15 return;
如果这条语句放在myresearch的第一行,会带来问题,第15次翻转会直接return而不会判断这次翻转之后的状态。应该是先判断状态,然后在索引超过15之后再返回。

代码非常简洁。建议可以即用BFS又用DFS,毕竟可以更熟悉两种之间的差异,和某些特定场合通用情况!

下面我将列出我的BFS算法,就是广度优先的,首先要强调一点,有些代码在当访问某个被访问过的状态的时候,居然去修改改状态的步长,那一步是永远不会被执行的,因为广度优先,后面如果访问到了前面访问过的状态,无论如何后面的步长也会比前面的长,所以看着这些代码,觉得应该思考什么是BFS,不要写出外行的代码来。

先贴代码:

#include <iostream>
#include<fstream>
#include<cstdlib>
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;

const int MAX = 16;
int final_step = MAX;
const int MAX_STATE = 65536;
int states[MAX_STATE];
int steps[MAX_STATE];


const unsigned int flips[] = {19,39,78,140,305,626,1252,2248,
                                4880,10016,20032,35968,12544,29184,58368,51200};
unsigned int init(char str[]){
	final_step = MAX;
	memset(states,0,sizeof(states));
	memset(steps,0,sizeof(steps));
	unsigned int current_state = 0;
	for(int i=0;i<MAX;i++){
		if(str[i] == 'b'){
			current_state += (1 << i);
		}
	}
	return current_state;
}

int flip(unsigned int state,int index){
	return (state ^ flips[index]);	
}

bool mysearch(unsigned int state)
{
	int step = 0;
	queue<unsigned int> qu;
	qu.push(state);
	states[state] = 1;
	steps[state] = 0;

	unsigned int front = 0;
	while(!qu.empty())
	{
		front = qu.front();
		qu.pop();
		if(front == 0 || front == 65535)
		{
			final_step = steps[front];
			return true;
		}
		unsigned int next = 0;
		for(int i=0;i<MAX;i++)
		{
			next = flip(front,i);
			if(!states[next])
			{
				states[next] = 1;
				steps[next] = steps[front] + 1;
				qu.push(next);
			}
		}
		
	}
	return false;
}

int main(int argc, char** argv) {	
	
	char str[MAX];
	int number = 0;
	char c;
	while(cin >> c)
	{
		if(c==' ' || c == '\n')
		 continue;
		 str[number++] = c;
		 if(number == 16) break;
	}
	unsigned int current_state = init(str);
	bool ans = mysearch(current_state);
	if(!ans)
		cout << "Impossible";
	else
		cout << final_step;
	cout << endl;
	return 0;
}

思想很简单,就是遍历所有的状态空间0-65535每次都是从16个点中发散出去,有些人的代码很有意思的说,这个queue长度有限制的,所以自己数组操作,其实完全没有必要,STL的queue一样用的很好!每一个当前状态进入队列,然后对队列的首元素进行判断,如果首元素就是终止状态自然返回true,如果队列最后遍历完了,也没有发现当然返回false,如果某一部找到了出口,那这一步自然就是最短的步长,因为这是BFS,如果不是最短,那采用这种算法的人的思路就应该理一理了。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值