搜索训练1 [8数码问题]

HDU1043、以及POJ1077上面都有这道题目,可以说是搜索里的非常经典的题目了。

poj上面的数据真的是弱,由于只有一组数据,简单bfs直接就可以过掉。

前前后后捣鼓了能有6个小时,才把这道题目在HDU上以4500ms的微弱优势通过。。。。。。。

我的思路一开始是简单的bfs,在HDU上超时。然后改用A*搜索,尝试了3种启发函数,才微弱的过掉。。。。我看别人的代码也是A*算法,而且启发函数写的跟我一样,为什么就几百ms过掉了呢。。。。还是不明白。。。。


思路:

(1)本题比较关键的一点就是判重问题,怎么样保证同一个状态只能被访问一次,涉及到状态的表示问题。

而状态其实是一个排列数,我们现在想要把一个排列数Hash到一个整数上,这就用到了康托展开的方法。(见我前一个博文)

(2)第二个比较关键的地方就是启发函数的选取

启发函数设置了两个参数:

val:当前状态到目标状态的哈密顿距离。

step: 当前已经移动的次数。

函数1:val + step :在poj上以67ms通过,在HDU上TLE

函数2:step为第一关键字,val为第二关键字 在poj上以760ms通过,在HDU当然TLE

函数3:以val为第一关键字,以step为第二关键字,在poj上以0ms通过,在HDU上以4500ms通过


这里还有一个需要注意的地方,就是无解情况的判定:

有一个定理,当两个状态的逆序数的奇偶性相同的时候,他们可以互相到达。

否则,他们无法互相到达,这个定理可以快速完成无解判定。


#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
using namespace std;
class cantor{
	public:
	int fac_dp[11];
	int fac(int i){
		if(fac_dp[i]) return fac_dp[i];
		return fac_dp[i] = i*fac(i-1);
	}
	void init(){
		memset(fac_dp,0,sizeof(fac_dp));
		fac_dp[0] = 1;
		fac(10);
	}
	int encode(int num[],int n){
		int ans = 0;
		for(int i = 0;i < n;i++){
			int cnt = 0;
			for(int j = i;j < n;j++){
				if(num[i] > num[j])
					cnt++;
			}
			ans += fac_dp[n-i-1] * cnt;
		}
		return ans;
	}
	void decode(int ans[],int num,int n){
		int used[11];
		for(int i = 0;i < n;i++) used[i] = 0;
		for(int i = 0;i < n;i++){
			int cnt = num/fac_dp[n-i-1];
			int r = num%fac_dp[n-i-1];
			for(int j = 0;j < n;j++){
				if(!cnt && !used[j]){
					used[j] = 1;
					ans[i] = j;
					break;
				}
				if(!used[j]){
					cnt--;
				}
			}
			num = r;
		}
	}
};
struct node{
	int key;
	int val;
	int step;
	int loc;
	int x[9];
	friend bool operator<(node n1,node n2){
		if(n1.val == n2.val){
			return n1.step > n2.step;
		}
		return n1.val > n2.val;
	}
	node(int a,int b,int c,int loc):key(a),val(b),step(c),loc(loc){
	}
};
int a[11],b[11];
const int MAX = 3628800;
int pre[MAX];
int preid[MAX];
int visited[MAX];
int dx[4] = {1,-1,0,0};
int dy[4] = {0,0,-1,1};
int tarmp[9] = {8,0,1,2,3,4,5,6,7};
char dc[4] = {'d','u','l','r'};
cantor ct;
void prtpath(int end,int beg){
	char stk[100];
	int cnt = 0;
	while(end != beg){
		stk[cnt++] = dc[pre[end]]; 
		end = preid[end];
	}
	while(cnt){
		putchar(stk[--cnt]);
	}
	puts("");
}
bool check(int arr[]){
	int sum = 0;
	for(int i = 0;i < 9;i++){
		if(arr[i] == 0) continue;
		for(int j = i;j < 9;j++){
			if(arr[j] == 0) continue;
			if(arr[i] > arr[j]) sum++;
		}
	}
	return sum%2 == 0;
}
int calc(int arr[]){
	int ans = 0;
	for(int i = 0;i < 9;i++){
		ans += abs(i/3 - tarmp[arr[i]]/3) + abs(i%3 - (tarmp[arr[i]]%3));
	}
	return ans;
}
int main(){	
	ct.init();
	char c;
	while(~scanf(" %c",&c)){
		memset(visited,0,sizeof(visited));
		memset(pre,0,sizeof(pre));
		if(c == 'x') a[0] = 0;
		else a[0] = c - '0';
		for(int i = 1;i < 9;i++){	
			scanf(" %c",&c);
			if(c == 'x'){
				a[i] = 0;
			}
			else{
				a[i] = c - '0';
			}
		}
		if(!check(a)){
			puts("unsolvable");
			continue;
		}
		for(int i = 0;i < 8;i++) b[i] = i+1;
		b[8] = 0;
		int tar = ct.encode(b,9);
		int code = ct.encode(a,9);
		preid[code] = code;
		priority_queue<node> Q; 
		int lc = -1;
		while(a[++lc] != 0);
		node cur = node(code,calc(a),0,lc);
		for(int i = 0;i < 9;i++) cur.x[i] = a[i];
		Q.push(cur);
		visited[code] = 1;
		int f = 0;
		while(!Q.empty()){
			node nn = Q.top();Q.pop();
			int ccd = nn.key;
			if(ccd == tar){
				//找到了!  
				//cout<<"YES"<<endl;
				prtpath(tar,code);
				f = 1;
				break;
			}
			int lc = nn.loc;
			int x = lc / 3;
			int y = lc % 3;
			nn.step ++;
			for(int i = 0;i < 4;i++){
				int nx = dx[i] + x;
				int ny = dy[i] + y;
				if(nx >= 0 && nx < 3 && ny >= 0 && ny < 3){
					int nlc = nx * 3 + ny;
					swap(nn.x[lc],nn.x[nlc]);
					int ncd = ct.encode(nn.x,9);
					if(!visited[ncd]){
						pre[ncd] = i;
						preid[ncd] = ccd;
						nn.key = ncd;
						nn.val = calc(nn.x);
						nn.loc = nlc;
						Q.push(nn);
						visited[ncd] = 1;
					}
					swap(nn.x[lc],nn.x[nlc]);
				}
			}
		}
		if(!f){
			puts("unsolvable");
		}
	}
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值