【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;	
}


转载于:https://www.cnblogs.com/AWCXV/p/7632365.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值