终于被我水过了,哈哈,4640MS,真的是卡时间啊。。,先用这个水水,还没搞清楚什么地方耗太多的时间,别人好像也没用多少时间的啊。之后看了下A*算法,时间上的消耗就减少了很多。
题意:
给定一个序列,由1~8数字和字母x组成,表示的是一个3*3的矩形。每次操作x都能与相邻的数字交换,问如何操作才能使得序列为{1,2,3,4,5,6,7,8,x}。
题解:
1)双向BFS
这题需要特判无法变换到得情况,我们发现每次变换8个数字的逆序数奇偶性都是不变的,所以找出给定的序列的逆序数可以特判不存在的情况。
之后要了解下康托展开(一开始直接map,死的惨惨的),这个是为了哈希用的,就是压缩的排列,使得其变为第几个排列,那样就容易哈希了:对于14032,在排列中排第几个?是1*4!+3*3!+0*2!+1*1!+0*0!,为什么呢,类似于数位dp,第一位比1小的只有1个,后面可以随意排列,既4!种;接着确定第一位为1,第二位比4小的有4个,但1已经用过了,就只有3*3!种情况;接着确定前两位是14...以此类推。
通过康托展开,我们将x=0,那样所有的排列都可以转换成0到9!了,360000左右各值,很容易用数组哈希。
之后是双向BFS,跟单BFS用的一样,从开头和终点一起开始BFS,直到两个BFS相遇,就是最短的路程了。这样可以缩短很长的时间,why?例如一个迷宫,我们假设没有边界,没有墙的空地,单BFS需要4^n时间,那双向BFS就是4^(n/2)*2,缩短了很多。
2)A*算法
A*算法是一种求最短路径的算法,会添加一个评估函数h(n),以减少时间的消耗;BFS是一种特殊的A*算法,h(n)=0。
拓展方式跟BFS类似,不过采用优先队列,以f值小的优先(f=g+h,g为实际到这点的消耗,h为预测到目标点的消耗)。若h<=n(n为实际这点到目标点的消耗),必定存在最优解;若h>n则不一定存在最优解。所以h值越接近n值越好,但不要超过。
具体解释:A*算法-百度百科,A*算法入门-博客园
代码:
1.双向BFS
#include <iostream>
#include <cstdio>
#include <cstring>
#include <ctime>
#include <algorithm>
#include <cmath>
#include <queue>
#include <map>
#include <vector>
using namespace std;
const int maxn=5e5+10;
int hash[9]={1,1,2,6,24,120,720,5040,40320};
struct node{
int num;
char ch;
}pre[maxn];
int vis[maxn];
int vis2[maxn];
struct node2{
string c;
int num;
}e;
int dir[4]={-3,3,-1,1};
char a[30],d[10]={"udlr"},d2[10]={"durl"};
string b="123456780";
void show(int x)
{
if(pre[x].num==-1)return ;
show(pre[x].num);
printf("%c",pre[x].ch);
}
int get_hash(string e)//获得hash值,康托展开
{
int s=0,i,j,k;
for(i=0;i<9;i++)
{
k=0;
for(j=0;j<i;j++)
if(e[j]>e[i])k++;
s+=k*hash[i];
}
return s;
}
void bfs()
{
vis[get_hash(e.c)]=1;
pre[1].num=-1;
int num=2,x,i,j,k,p,q;
node2 f,g;
f.c=b;
f.num=8;
pre[2].num=-1;
vis2[get_hash(f.c)]=2;
queue<node2>q1;
queue<node2>q2;
q1.push(e);
q2.push(f);
while(!q1.empty()&&!q2.empty())
{
f=q1.front();
q1.pop();
//cout<<f.c<<endl;
p=get_hash(f.c);
if(vis2[p])
{
show(vis[p]);
k=vis2[p];
while(pre[k].num!=-1)
{
printf("%c",pre[k].ch);
k=pre[k].num;
}
printf("\n");
return ;
}
for(i=0;i<4;i++)
{
if(i==0&&f.num<3)continue;
if(i==1&&f.num>5)continue;
if(i==2&&f.num%3==0)continue;
if(i==3&&f.num%3==2)continue;
x=f.num+dir[i];
g=f;
swap(g.c[f.num],g.c[x]);
q=get_hash(g.c);
if(vis[q])continue;
vis[q]=++num;
g.num=x;
pre[num].num=vis[p];
pre[num].ch=d[i];
q1.push(g);
}
f=q2.front();
q2.pop();
p=get_hash(f.c);
//cout<<f.c<<endl;
if(vis[p])
{
show(vis[p]);
k=vis2[p];
while(pre[k].num!=-1)
{
printf("%c",pre[k].ch);
k=pre[k].num;
}
printf("\n");
return ;
}
for(i=0;i<4;i++)
{
if(i==0&&f.num<3)continue;
if(i==1&&f.num>5)continue;
if(i==2&&f.num%3==0)continue;
if(i==3&&f.num%3==2)continue;
x=f.num+dir[i];
g=f;
swap(g.c[f.num],g.c[x]);
q=get_hash(g.c);
if(vis2[q])continue;
vis2[q]=++num;
g.num=x;
pre[num].num=vis2[p];
pre[num].ch=d2[i];
q2.push(g);
}
}
printf("unsolvable\n");
}
int main()
{
//freopen("C:\\Documents and Settings\\Administrator\\桌面\\in.txt","r",stdin);
//freopen("C:\\Documents and Settings\\Administrator\\桌面\\out.txt","w",stdout);
while(gets(a))
{
int i,j,k=0,n;
n=strlen(a);
e.c="";
for(i=0,j=0;i<n;i++)
if(a[i]!=' ')
{
if(a[i]=='x'){e.num=j;e.c+='0';}
else e.c+=a[i];
j++;
}
for(i=0;i<9;i++)
{
//printf("%c\n",e.c[i]);
if(e.c[i]=='0')continue;
for(j=0;j<i;j++)
{
if(e.c[j]=='0')continue;
if(e.c[j]>e.c[i])k++;
}
}
memset(vis2,0,sizeof(vis2));
memset(vis,0,sizeof(vis));
if(k&1)printf("unsolvable\n");
else bfs();
//printf("%d %c\n",pre[3].num,pre[3].ch);
}
return 0;
}
2.A*算法:
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <algorithm>
#include <iostream>
#include <queue>
#include <map>
#include <vector>
using namespace std;
const int maxn=4e5+10;
int ha[9]={1,1,2,6,24,120,720,5040,40320};
int dir[4][2]={{-1,0},{1,0},{0,-1},{0,1}};
char d[10]="udlr";
int vis[maxn];
struct node{
int f[3][3];
int x,y;
int g,h;
int hash_num;
bool operator < (const node a)const{
//return h==a.h?g>a.g:h>a.h; //281MS
return h+g>a.h+a.g; //859ms
}
};
struct path{
int pre;
char ch;
}p[maxn];
int get_hash(node e)//康托展开,压缩空间。
{
int a[9],i,j,k=0,ans=0;
for(i=0;i<3;i++)
{
for(j=0;j<3;j++)
a[k++]=e.f[i][j];
}
for(i=0;i<9;i++)
{
k=0;
for(j=0;j<i;j++)
if(a[j]>a[i])k++;
ans+=ha[i]*k;
}
return ans;
}
//评估函数,获得评估值
//计算1~8的数字回到原点需要的步数作为评估值,必定小于实际操作数
int get_h(node e)
{
int i,j,ans=0;
for(i=0;i<3;i++)
{
for(j=0;j<3;j++)
{
if(e.f[i][j])
ans+=abs(i-(e.f[i][j]-1)/3)+abs(j-(e.f[i][j]-1)%3);
}
}
return ans;
}
void print(int x)
{
if(p[x].pre==-1)return;
print(p[x].pre);
printf("%c",p[x].ch);
}
void A_star(node e)
{
memset(vis,0,sizeof(vis));
int i,j,k,end_ans,xx,yy;
node a,b;
for(i=0;i<9;i++)
a.f[i/3][i%3]=(i+1)%9;
end_ans=get_hash(a);
e.hash_num=get_hash(e);
e.g=0;e.h=get_h(e);
vis[e.hash_num]=1;
p[e.hash_num].pre=-1;
if(e.hash_num==end_ans){printf("\n");return;}
priority_queue<node>q;
q.push(e);
while(!q.empty())
{
e=q.top();
q.pop();
//printf("%d\n",e.hash_num);
for(i=0;i<4;i++)
{
xx=e.x+dir[i][0];
yy=e.y+dir[i][1];
if(xx<0||yy<0||xx>=3||yy>=3)continue;
a=e;
swap(a.f[e.x][e.y],a.f[xx][yy]);
k=get_hash(a);
if(vis[k])continue;
vis[k]=1;
a.hash_num=k;
a.x=xx;
a.y=yy;
a.g++;
a.h=get_h(a);
p[k].pre=e.hash_num;
p[k].ch=d[i];
if(k==end_ans)
{
print(k);
printf("\n");
return ;
}
q.push(a);
}
}
}
int main()
{
/* node e;
e.f[0][0]=2;e.f[0][1]=3;e.f[0][2]=4;
e.f[1][0]=1;e.f[1][1]=5;e.f[1][2]=0;
e.f[2][0]=7;e.f[2][1]=6;e.f[2][2]=8;
printf("*%d\n",get_hash(e));*/
char a[30];
while(gets(a))
{
int i,j,k,n;
node e;
n=strlen(a);
for(i=0,j=0;i<n;i++)
{
if(a[i]==' ')continue;
if(a[i]=='x'){e.f[j/3][j%3]=0;e.x=j/3;e.y=j%3;}
else e.f[j/3][j%3]=a[i]-'0';
j++;
}
//判断逆序数
for(i=0,k=0;i<9;i++)
{
if(e.f[i/3][i%3]==0)continue;
for(j=0;j<i;j++)
{
if(e.f[j/3][j%3]==0)continue;
if(e.f[j/3][j%3]>e.f[i/3][i%3])k++;
}
}
if(k&1)printf("unsolvable\n");
else A_star(e);
}
return 0;
}