题目地址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;
}