「USACO3.2.5」魔板 - 广度优先搜索优化

题目描述

在成功地发明了魔方之后,拉比克先生发明了它的二维版本,称作魔板。这是一张有8个大小相同的格子的魔板:
           1 2 3 4

           8 7 6 5

我们知道魔板的每一个方格都有一种颜色。这 8 种颜色用前 8 个正整数来表示。可以用颜色的序列来表示一种魔板状态,规定从魔板的左上角开始,沿顺时针方向依次取出整数,构成一个颜色序列。对于上图的魔板状态,我们用序列(1,2,3,4,5,6,7,8)来表示。这是基本状态。

这里提供三种基本操作,分别用大写字母“A”,“B”,“C”来表示(可以通过这些操作改变魔板的状态):

          “A”:交换上下两行;

   “B”:将最右边的一行插入最左边;

   “C”:魔板中央作顺时针旋转。

下面是对基本状态进行操作的示范:

             A:8 7 6 5
       1 2 3 4
    B:4 1 2 3
       5 8 7 6
    C:1 7 2 4
       8 6 3 5

对于每种可能的状态,这三种基本操作都可以使用

输入格式

只有一行,包括8个整数,用空格分开(这些整数在范围 1——8 之间),表示目标状态。

输出格式

Line 1: 包括一个整数,表示最短操作序列的长度。

Line 2: 在字典序中最早出现的操作序列,用字符串表示,除最后一行外,每行输出60个字符。

样例输入

 

2 6 8 4 5 7 3 1

样例输出

 

7

BCABCCB

分析

看到题目,应该是很容易想到广搜的,搜索主体程序也比较容易写,就是如何判重,这是本题的一个难点。我们考虑将每一种状态转化为一个8位十进制数,可这样最大的一种(87654321)也有可能会爆内存。实际上总共的状态数量也只有8!=40320(x!为x的阶乘,即1*2*3*...*(x-1)*x)个,可我们为什么要开到87654321这么大的数组呢?这是因为这其中有很多不可能会出现发情况,比如66666666,这就造成了空间浪费。因此,考虑将每一种状态对应其全排列的序数,这样就只需要开40320的数组。怎么做到呢?这里可以用康托展开,也可以用另一种将排列转序数的方法,当然,如果不嫌时间慢,暴力枚举也可以。其实,这样也是一种hash判重的算法。

代码

#include <iostream>
#include <cmath>
using namespace std;
int now=12345678;//定义初始状态 
int goal;//目标状态 
int book[40325];//判重用数组 
struct node {
	int s,f,data;
	char way;
}que[40325];
int f(int a) {//hash函数 
	int arr[9];
	for (int i=int(pow(10,7)),j=1;i>=1;i/=10,j++) {//将数字转化为数组 
		arr[j]=a/i;
		a%=i;
	}
	int num=0,k=1;
	for (int i=7;i>=1;i--) {
		for (int j=i+1;j<=8;j++) {
			if (arr[j]<arr[i]) num+=k;
		}
		k*=9-i;
	}
	return num+1;
}
int actionA(int a) {//三种操作 
	int b=0;
	for (int i=int(pow(10,7)),j=1;i>=1;i/=10,j*=10) {
		int p=a/i;
		b+=p*j;
		a%=i;
	}
	return b;
}
int actionB(int a) {
	int arr[9];
	for (int i=int(pow(10,7)),j=1;i>=1;i/=10,j++) {
		arr[j]=a/i;
		a%=i;
	}
	arr[0]=arr[4];
	for (int i=4;i>=2;i--) arr[i]=arr[i-1];
	arr[1]=arr[0];
	arr[0]=arr[5];
	for (int i=5;i<=8;i++) arr[i]=arr[i+1];
	arr[8]=arr[0];                                                       
	a=0;
	for (int i=int(pow(10,7)),j=1;i>=1;i/=10,j++)
		a+=arr[j]*i;
	return a;
}
int actionC(int a) {
	int arr[9];
	for (int i=int(pow(10,7)),j=1;i>=1;i/=10,j++) {
		arr[j]=a/i;
		a%=i;
	}
	swap(arr[6],arr[7]);
	swap(arr[3],arr[6]);
	swap(arr[2],arr[3]);
	a=0;
	for (int i = int(pow(10,7)),j=1;i>=1;i/=10,j++)
		a+=arr[j]*i;
	return a;
}
void print(int k) {//递归输出路径 
	if (que[k].f) print(que[k].f);
	if (que[k].way != 0) cout<<que[k].way;
}
int main()
{
	for (int i=1;i<=8;i++) {
		int _n;
		cin>>_n;
		goal+=_n*int(pow(10,8-i));
	}
	if (goal==now) {
		cout<<0<<endl;
		return 0;
	}
	int head,tail;//广搜主体 
	head=tail=1;
	que[tail].data=now;
	que[tail].f=0;
	que[tail].s=0;
	book[f(now)]=1;
	tail++;
	while (head<tail) {
		for (char k='A';k<='C';k++) {
			int dt;
			switch(k) {
				case 'A':
					dt=actionA(que[head].data);
					que[tail].way='A';
					break;
				case 'B':
					dt=actionB(que[head].data);
					que[tail].way='B';
					break;
				case 'C':
					dt=actionC(que[head].data);
					que[tail].way='C';
					break;
			}
			if (book[f(dt)]) continue;
			book[f(dt)]=1;
			que[tail].data=dt;
			que[tail].s=que[head].s+1;
			que[tail].f=head;
			if (dt==goal) {
				cout<<que[tail].s<<endl;
				print(tail);
				return 0;
			}
			tail++;
		}
		head++;
	}
	return 0;
}

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值