ZOJ 1505跳子棋

题目地址ZOJ-1505


       搜索问题,开始时是用STL,但效率太低。后改用一维数组,将二维坐标(row,col)映射成一维来存储,同时在使用一个哈希表用于记忆化搜索,哈希表的key是四个棋子坐标的编码,value为当前已经找到的到达目标格局需要移动的最少步数。

       下面是用深搜来做的,效率比较低,即使加个缓存仍然做很多重复的搜索,在ZOJ上测试时间为2秒,当初始格局与终止格局相差比较大时,搜索时间比较长。

       

#include<fstream>
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<map>
#include<set>
#include<queue>
using namespace std;

int orig[4], dest[4];  //point(x,y) --> 8*(x-1)+y
int x[4] = {-1,0,1,0},y[4] = {0,1,0,-1};  //上右下左移动一格
map<int, int> cache;  //key = (dest[0]dest[1]dest[2]dest[3]) 64进制编码, value为目前已知到达目标格局花费的最少步数

bool dfs(int step){
	int bound = 0;
	for(int k=0;k<4;k++){
		if(find(orig,orig+4,dest[k])==orig+4) bound++;
	}
	if(bound>step) return false;  //剪枝条件,至少需要移动bound步
	if(step==0) return true;  //step==0 && bound!=0则提前return false
    
	int  *a = new int[4];
    copy(dest,dest+4,a);
	sort(a,a+4);
	int key = a[0]*65*65*65 + a[1]*65*65 + a[2]*65 + a[3]; //对dest编码
	
	if(cache.find(key)!=cache.end() && cache[key]<=step) return true; 

	for(int i=0;i<4;i++){
		int row = (dest[i]-1)/8 + 1, col = dest[i] - (row-1)*8;
		for(int j=0;j<4;j++){
			int r = row, c = col;
			r += x[j]; c += y[j];
			if(r<1||r>8||c<1||c>8) continue;
			if(find(dest, dest+4, 8*(r-1)+c)!=dest+4){ //当前方向上有个棋子挡着
				r += x[j]; c += y[j];
				if(r<1||r>8||c<1||c>8||find(dest, dest+4,8*(r-1)+c)!=dest+4) continue;
			}

			int tmp = 8*(r-1)+c;
            
			swap(dest[i],tmp);
			if(dfs(step-1)){
				if(cache.find(key)==cache.end()) cache[key] = step;
				else cache[key] = min(cache[key],step);
				delete a;    //注意回收辣鸡
				return true;
			}
			swap(dest[i],tmp);
		}
	}
    
	delete a;  
	return false;
}


int main(){
	
	freopen("input.txt","rb",stdin);
    int i,j;
	while(true){
	for(int k=0;k<4;k++){
		if(scanf("%d%d",&i,&j)!=2) return 0;
		dest[k] = 8*(i-1)+j;
	}
	for(int k=0;k<4;k++){
		scanf("%d%d",&i,&j);
		orig[k] = 8*(i-1)+j;
	}

    if(dfs(8)) cout<<"YES"<<endl;
	else cout<<"NO"<<endl;  
	cache.clear(); 
   } 

	//make_sample();
	return 0;
}

下面是更有效率的双向宽搜,为节省时间和空间同时压缩状态,同时设置缓存表以避免重复搜索,可以说基本没有重复搜索,搜索速度非常快。在ZOJ上测试时间110ms,内存800KB,比深搜内存大一点,但速度快20倍。


#include<fstream>
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<map>
#include<set>
#include<queue>
using namespace std;

int orig[4], dest[4];  //point(x,y) --> 8*(x-1)+y
int x[4] = {-1,0,1,0},y[4] = {0,1,0,-1};  //上右下左移动一格

//编码
inline int encode(int* a){
	set<int> tmp;
	int r = 0;
	for(int i=0;i<4;i++) tmp.insert(a[i]);
	for(set<int>::iterator it = tmp.begin();it!=tmp.end();++it) r = 65*r + *it;
	return r;
}

//解码
inline int* decode(int key){
	int *r = new int[4];
	r[3] = key % 65; key/=65;
	r[2] = key % 65; key/=65;
	r[1] = key % 65; key/=65;
	r[0] = key % 65;
	return r;
}
//双向宽度搜索
bool bfs(int maxStep){
	queue<int> Q1, Q2;
	int step1 = 0, step2 = 0;  //头尾搜的步数
	Q1.push(encode(orig)); Q1.push(-1);   //-1作为不同层之间的分界线
	Q2.push(encode(dest)); Q2.push(-1); 
    
	set<int> tab1,tab2;  //缓存当前层格局的编码,用于查询是否成功
	tab1.insert(Q1.front());
	tab2.insert(Q2.front());

	while(true){ 
		if(step1 + step2 > maxStep) return false;
		
		while(!Q1.empty()){ 
			if(Q1.front()==-1){
				Q1.pop(); Q1.push(-1);
			    step1++; 
				if(step1 + step2 > maxStep) return false;
				break; //头部当前层已搜索完毕,接下来搜索尾部当前层
		    }
		    else{
				int key = Q1.front();
				int *a = decode(key);  
				if(tab2.find(key)!=tab2.end()) return true;
			    Q1.pop();
               
			    for(int i=0;i<4;i++){
					int row = (a[i]-1)/8 + 1, col = a[i] - 8*(row-1);
				    for(int j=0;j<4;j++){ //up,right,down,left
						int r = row + x[j], c = col + y[j];
					    if(r<1||r>8||c<1||c>8) continue;  //当前方向上无后继格子
					
					    if(find(a,a+4,8*(r-1)+c) != a+4){ //如果当前方向上的后继格子有棋子挡着,则只能跳过该棋子
							r += x[j]; c += y[j];
						    if(r<1||r>8||c<1||c>8||find(a,a+4,8*(r-1)+c)!=a+4) continue;
					    }
					    //将棋子从(row,col)跳到(r,c)
                        int newKey = 8*(r-1)+c;
					    swap(a[i],newKey);
						int t = encode(a);

						if(tab1.find(t)==tab1.end()){  //去重
							Q1.push(t); //将新的格局编码压入队列
						    tab1.insert(t);
						}

					    swap(a[i],newKey);  //交换回来
					}
				}

				delete a; //释放内存
			}
		}
        
		while(!Q2.empty()){ 
			if(Q2.front()==-1){
				Q2.pop(); Q2.push(-1);
				step2++; break;
			}
			else{
				int key = Q2.front(); 
				Q2.pop(); 
				if(tab1.find(key)!=tab1.end()) return true;  //已经成功找到
				int *a = decode(key);

				for(int i=0;i<4;i++){
					int row = (a[i]-1)/8 + 1, col = a[i] - 8*(row-1);
					for(int j=0;j<4;j++){
						int r = row + x[j], c = col + y[j];
						if(r<1||r>8||c<1||c>8) continue;
						if(find(a,a+4,8*(r-1)+c)!=a+4){
							r += x[j]; c += y[j];
							if(r<1||r>8||c<1||c>8||find(a,a+4,8*(r-1)+c)!=a+4) continue;
						}

						int newKey = 8*(r-1) + c;
						swap(a[i],newKey);
						int t = encode(a);
						
						if(tab2.find(t)==tab2.end()){ //去重
							Q2.push(t);
							tab2.insert(t);
						}

						swap(a[i],newKey);
					}
				}

				delete a;
			}
		}
	}
}

int main(){
	
	freopen("input.txt","rb",stdin);
    int i,j;
	while(true){
	for(int k=0;k<4;k++){
		if(scanf("%d%d",&i,&j)!=2) return 0;
		dest[k] = 8*(i-1)+j;
	}
	for(int k=0;k<4;k++){
		scanf("%d%d",&i,&j);
		orig[k] = 8*(i-1)+j;
	}
    if(bfs(8)) cout<<"YES"<<endl;
	else cout<<"NO"<<endl;

	//make_sample();
	return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值