HDU 1043 Eight(A* + 奇偶剪枝 + 康拓展开)



题目链接:Here!

题意描述:经典八数码问题,给定八数码的初始序列,求经过u、r、l、d四种操作到达1 2 3 4 5 6 7 8 x的状态,打印出操作序列?

代码:


/*
	Note:
		例题:HDU 1043 Eight
		 
		方法二:A*(h(x):曼哈顿距离)+奇偶剪枝(逆序数)+康拓展开 
		分析:
			1、首先移动x时序列(把x除外)的逆序数奇偶性不会发生变化,左右移动序列不变,上下移动每次某个数会向前移动两位或向后移动两位逆序数+2,所以奇偶性不变
			2、给定一个序列我们可以发现要到达目标序列:1 2 3 4 5 6 7 8 x至少需要的距离为当前序列每个数移动到目标序列中该数的位置的步数之和,故可以以此作为估计函数
			3、判断是否重复出现可以使用康拓展开
		
		补充:
			逆序数对于n个不同的元素,先规定各元素之间有一个标准次序(例如n个 不同的自然数,可规定从小到大为标准次序),于是在这n个元素的任一排列中,当某两个元素
		的先后次序与标准次序不同时,就说有1个逆序。一个排列中所有逆序总数叫做这个排列的逆序数。 
			逆序数为偶数的称为偶排列,逆序数为奇数的称为奇排列 
		
		此题奇偶剪枝的原理:
			因为目标状态的逆序数是0,然后左右移动x逆序数不变,上下移动x逆序数+2,逆向思维,就和反向BFS打表的思路一样,那么,反向推出的一些状态逆序数必然是偶数,
		也就是说能够到达目标状态的初始状态逆序数必然为偶数 
*/
#include<cstdio>
#include<cstring>
#include<string>
#include<iostream>
#include<cmath>
#include<queue>
#include<cstdlib>
using namespace std;
//#define test

int fac[]={1,1,2,6,24,120,720,5040,40320};
int Cantor(int* s,int n){
	int ans=0;
	for(int i=0;i<n;i++){
		int tmp=0;
		for(int j=i+1;j<n;j++)
			if(s[i]>s[j])	tmp++;
		ans += tmp*fac[n-1-i];
	}
	return ans;
}

const int maxn = 362880;
const int dx[]={-1,0,1,0};
const int dy[]={0,1,0,-1};
bool  can[maxn];
int   pre[maxn];			// pre[]父节点	parents 
char  charop[maxn];			
int   tt[9]; 
struct Node{
	int state[9];
	int key,x,y;
	int g,h;				// f(n) = h(n) + g(n)	g:走到当前位置走过的步数   h:估计距离(曼哈顿路径) 
	Node(){}				// 缺省构造函数,利于下面定义新的结构体变量 
	Node(int _key,int _x,int _y,int _g,int _h):key(_key),x(_x),y(_y),g(_g),h(_h){}
};
struct cmp{				// 声明优先队列优先级 , 类似小顶堆 
	bool operator()(const Node& a,const Node& b){
		return (a.g+a.h)>(b.g+b.h);	
	}
}; 
// 得到目前状态到目标状态的最少估计步数,采用曼哈顿路径作为估值函数 
int goal_x[]={2,0,0,0,1,1,1,2,2},goal_y[]={2,0,1,2,0,1,2,0,1}; 
int get_h(int* state){
	int h=0;
	for(int i=0;i<9;i++)
		if(state[i])
			h += ( abs(i/3-goal_x[ state[i] ]) + abs(i%3-goal_y[ state[i] ]) );	
	return h;
}

bool judge(int x,int y){
	if(x>=0 && x<3 && y>=0 && y<3)	return true;
	return false;
} 

char ans[100];
char operation[]={'u','r','d','l'};	
int  target;
int  anstol;
int  d[9],tg[]={1,2,3,4,5,6,7,8,0};
bool ok; 
void bfs(int s,int x,int y){		// 传入初始状态的Cantor值和X的位置(x,y) 
	memset(can,false,sizeof(can));
	priority_queue<Node,vector<Node>,cmp> q;
	can[s]=true;
	pre[s]=-1;
	
	Node tmp(s,x,y,0,get_h(d));		
	for(int i=0;i<9;i++)	tmp.state[i]=d[i];
	q.push(tmp);
	
	while(!q.empty()){
		tmp=q.top();	q.pop();
	//	printf("tmp information : (%d,%d) d = %d h = %d d+h= %d\n",tmp.x,tmp.y,tmp.d,tmp.h,tmp.d+tmp.h); 
		if(tmp.key==target){		// 找到目标输出operation 
			anstol=0;
			int key=target;
			while(pre[key]!=-1){
				ans[ anstol++ ] = charop[key];
				key = pre[key];
			}
			ok=true;	return; 
		}
		Node next;
		for(int i=0;i<4;i++){
			next.x=tmp.x+dx[i] , next.y=tmp.y+dy[i];
			for(int j=0;j<9;++j) next.state[j]=tmp.state[j];
			
			if( judge(next.x,next.y) ){
				swap(next.state[tmp.x*3+tmp.y],next.state[next.x*3+next.y]);	// "走一步" 
				next.key=Cantor(next.state,9);
				if(!can[next.key]){
					can[next.key]=true;
					next.g = tmp.g+1;
					next.h = get_h(next.state);
					pre[next.key] = tmp.key;
					charop[next.key] = operation[i]; 	// ***
					q.push(next);
				}
			}
		}
	} 
}

bool ischeck(){		// 奇偶剪枝
	int flag=0;
	for(int i=0;i<9;i++){
		if(d[i]==0)	continue;
		for(int j=i+1;j<9;j++)
			if( d[j] && d[i]>d[j] )	flag++;
	}
	if(flag&1)	return true;	
	return false;				 
}
int main(){
	#ifdef test
		freopen("A-start Case.txt","r",stdin);
	//	freopen("A-start output Case.txt","w",stdout); 
	#endif
	target=Cantor(tg,9); 
	string op; 
    while(getline(cin,op)){  
        int k=0,sx,sy;
		for(int i=0;i<op.size();i++){
        	if(op[i]>='1' && op[i]<='8')	d[k++]=op[i]-'0';
			if(op[i]=='x'){
				d[k++]=0 ; 
				sx=(k-1)/3 , sy=(k-1)%3 ;
			}
		}
        if(ischeck()){ 	printf("unsolvable\n"); continue; }  
        ok=false;
        bfs(Cantor(d,9),sx,sy); 
		if(ok){
			for(int i=anstol-1;i>=0;--i)  
            	    printf("%c",ans[i]);  
        	printf("\n");
        }
        else	printf("unsolvable\n");
    }  
    return 0;  
} 
代码转载自:http://blog.csdn.net/mengxingyuanlove/article/details/49049777

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值