HDU1043&3567 搜索(八数码,康托展开+双向bfs+A*)
八数码问题,类似华容道,要求给出复位的操作序列。
康托展开用于解决排列相关问题,实现了一个排列到整数双射。因此我们可以用一个整数表示当前排列,便于进行搜索。以下是康托展开和逆展开的模板。
int cantor(int* st){
int x=0;
for(int i=0;i<9;i++){
int smaller=0;
for(int j=i+1;j<9;j++){
if(st[j]<st[i])
smaller++;
}
x+=fac[8-i]*smaller;
}
return x;
}
void decantor(int x){
vector<int> v;
for(int i=1;i<=9;i++)
v.push_back(i);
for(int i=8;i>=0;i--){
int r=x%fac[i];
int t=x/fac[i];
x=r;
sort(v.begin(),v.end());
tmp[8-i]=v[t];
v.erase(v.begin()+t);
}
}
题目打算采用bfs,从0状态出发寻找初始状态,途中每个状态都存储当前cmd序列和9的位置。但是这份代码最终mle了。。。
之后得知,路径最长不会超过三十多,则将路径储存在一个全局数组中path[9!][50],空间缩小将近一半,但出现超时的问题,考虑剪枝:在移动x的过程中逆序数的变化为偶数,而由于目标序列逆序数为0,所以初始逆序数如果是奇数,则可直接排除。
依然超时,推测是因为数据组数多,bfs要打表了。之前一直都是当作直跑一次写的代码。打算干脆放下去学A*。
A*搜索的原理很符合逻辑,大概地判断当前情况的优劣,并以此为排序权重影响搜索的顺序。同时将x节点所代表的值由9改为0,方便计算逆序数。
AC代码:
#include<bits/stdc++.h>
using namespace std;
static const int fac[10]={1,1,2,6,24,120,720,5040,40320,362880};
int sp[9];
int target[9]={1,2,3,4,5,6,7,8,0};
bool vis[362880];
int step[4][2]={{1,0},{0,1},{-1,0},{0,-1}};
char cmd[5]="drul";
int spos;
char path[362880];
int bck[362880];
void disp(int n){
if(bck[n]){
disp(bck[n]);
printf("%c",path[n]);
}
}
struct state{
int dat;
int pos;
int h,g,f;
state(){}
state(int _dat,int _pos,int _h,int _g):dat(_dat),pos(_pos),h(_h),g(_g){
f=g+h;
}
bool operator<(const state& st)const{
if(f!=st.f)
return f>st.f;
else
return g>st.g;
}
};
int geth(int s[]){
int res=0;
for(int i=0;i<9;i++){
if(s[i]==0)
continue;
int x=i/3; //当前位置
int y=i%3;
int tx=(s[i]-1)/3; //这个位置上的值,减一即为目标位置
int ty=(s[i]-1)%3;
res+=abs(x-tx)+abs(y-ty);
}
return res;
}
int cantor(int* st){
int x=0;
for(int i=0;i<9;i++){
int smaller=0;
for(int j=i+1;j<9;j++){
if(st[j]<st[i])
smaller++;
}
x+=fac[8-i]*smaller;
}
return x;
}
void decantor(int x,int* a){
vector<int> v;
for(int i=0;i<9;i++)
v.push_back(i);
for(int i=8;i>=0;i--){
int r=x%fac[i];
int t=x/fac[i];
x=r;
sort(v.begin(),v.end());
a[8-i]=v[t];
v.erase(v.begin()+t);
}
}
int cal(int* s){
//计算逆序数 reverse order number
int res=0;
for(int i=1;i<9;i++){
if(!s[i])
continue;
int tmp=0;
for(int j=0;j<i;j++)
if(s[j]>s[i])
tmp+=1;
res+=tmp;
}
return res;
}
int s[9]={0};
void bfs(){
memset(vis,0,sizeof(vis));
memset(path,0,sizeof(path));
memset(bck,0,sizeof(bck));
int dest=cantor(target);
memcpy(s,sp,9*sizeof(int));
int ron=cal(sp)-cal(target);
if(ron&1){
puts("unsolvable");
return;
}
priority_queue<state> q;
state st;
st.dat=cantor(s);
st.pos=spos;
st.g=0;
st.f=0;
st.h=0;
q.push(st);
vis[st.dat]=1;
while(!q.empty()){
state now=q.top();
if(now.dat==dest){
if(now.g==0)
puts("lr");
else{
disp(dest);
puts("");
}
return;
}
q.pop();
int x=now.pos/3;
int y=now.pos%3;
for(int i=0;i<4;++i){
int nx=x+step[i][0];
int ny=y+step[i][1];
if(nx<0||nx>2||ny<0||ny>2)
continue;
decantor(now.dat,s);
swap(s[x*3+y],s[nx*3+ny]);
int ndat=cantor(s);
if(!vis[ndat]){
int h=geth(s);
vis[ndat]=1;
bck[ndat]=now.dat;
path[ndat]=cmd[i];
q.push(state(ndat,nx*3+ny,h,now.g+1));
}
swap(s[x*3+y],s[nx*3+ny]);
}
}
cout<<"unsolvable"<<endl;
}
int main(){
char c;
while(cin>>c){
if(c=='x'){
spos=0;
sp[0]=0;
}
else
sp[0]=c-'0';
for(int i=1;i<9;i++){
cin>>c;
if(c=='x')
spos=i,sp[i]=0;
else
sp[i]=c-'0';
}
bfs();
}
return 0;
}
/*
1 2 3 4 5 6 7 8 x
2 1 3 4 5 6 7 8 x
1 3 x 4 2 6 7 5 8
1 2 3 4 5 6 7 x 8
*/
接下来是HDU3567,这一题比前面那道区别在于,给出T个测试,每次任意给起点终点,要求输出最短字典序最小的操作序列。
考虑双向bfs,前半段的字典序可以通过控制每层遍历时的移动方向决定,后半段则只能通过枚举所有最短的序列。由于bfs的特点,每次循环的层数代表了序列的长度,这样一层一层找下去肯定首先找到最短的,再在其中寻找字典序最小。
建立两个队列,每次先对q1进行遍历,寻找有没有状态已从后半段抵达;再对q2遍历,每次遇到后半段已到达的则说明,这是这次遍历的成果,尝试进行字典序更新。
代码如下,蜜汁WA调不出来了,大概思路是这样的:
#include<stdio.h>
#include<memory.h>
#include<queue>
#include<algorithm>
#include<iostream>
#include<string>
#include<cstring>
#include<map>
#include<vector>
using namespace std;
//升级八数码
int fac[]={1,1,2,6,24,120,720,5040,40320,362880};
int cantor(int* st){
int x=0;
for(int i=0;i<9;i++){
int smaller=0;
for(int j=i+1;j<9;j++){
if(st[j]<st[i])
smaller++;
}
x+=fac[8-i]*smaller;
}
return x;
}
struct node{
int hsh;
int pos;
int s[9];
};
int vis[362885];
string path[362885];
char cmd[]="dlru";
int dx[4]={1,0,0,-1};
int dy[4]={0,-1,1,0};
int dx2[4]={-1,0,0,1};
int dy2[4]={0,1,-1,0};
string bfs(node startP,node endP){
if(startP.hsh==endP.hsh)
return "";
queue<node>q1;
queue<node>q2;
memset(vis,0,sizeof vis);
q1.push(startP);
vis[startP.hsh]=1;
path[startP.hsh]="";
q2.push(endP);
vis[endP.hsh]=2;
path[endP.hsh]="";
while(!q1.empty()&&!q2.empty()){
int sz1=q1.size();
for(int i=0;i<sz1;i++){
node now=q1.front();
q1.pop();
int x=now.pos/3;
int y=now.pos%3;
//cout<<path[46232]<<endl;
//cout<<p<<endl;
for(int j=0;j<4;j++){
int nx=x+dx[j];
int ny=y+dy[j];
if(nx>=0&&nx<3&&ny>=0&&ny<3){
node n=now;
n.pos=nx*3+ny;
swap(n.s[now.pos],n.s[n.pos]);
n.hsh=cantor(n.s);
string p=path[now.hsh]+cmd[j];
if(vis[n.hsh]==2){
//cout<<p<<endl;
return p+path[n.hsh];
}else if(vis[n.hsh]==0){
vis[n.hsh]=1;
path[n.hsh]=p;
//cout<<path[n.hash]<<' '<<cmd[j]<<endl;
//cout<<path[46232]<<endl;
q1.push(n);
}
}
}
}
int sz2=q2.size();
for(int i=0;i<sz2;i++){
node now=q2.front();
q2.pop();
int x=now.pos/3;
int y=now.pos%3;
for(int j=0;j<4;j++){
int nx=x+dx2[j];
int ny=y+dy2[j];
if(nx>=0&&nx<3&&ny>=0&&ny<3){
node n=now;
n.pos=nx*3+ny;
swap(n.s[now.pos],n.s[n.pos]);
n.hsh=cantor(n.s);
string p=cmd[j]+path[now.hsh];
if(vis[n.hsh]==0){
path[n.hsh]=p;
vis[n.hsh]=2;
q2.push(n);
}else if(vis[n.hsh]==2){
if(p<path[n.hsh])
path[n.hsh]=p;
}
}
}
}
}
return "unsolvable";
}
int main(){
#ifdef LOCAL
freopen("data.in","r",stdin);
freopen("data.out","w",stdout);
#endif
int t;
cin>>t;
char s[10],e[10];
for(int i=1;i<=t;i++){
scanf("%s%s",s,e);
node S,E;
for(int i=0;i<9;i++){
if(s[i]=='X')
S.pos=i,S.s[i]=9;
else
S.s[i]=s[i]-'0';
if(e[i]=='X')
E.s[i]=9,E.pos=i;
else
E.s[i]=e[i]-'0';
}
S.hsh=cantor(S.s);
E.hsh=cantor(E.s);
string res=bfs(S,E);
printf("Case %d: %d\n",t,res.size());
cout<<res<<endl;
}
return 0;
}
/*
2
12X453786
12345678X
564178X23
7568X4123
1
1234X6758
12345678X
*/