八数码问题,就是手机上那个智能拼图的小游戏感觉很小的时候在诺基亚上玩过。只有一个块可以上下左右移动,问能否拼好。
IDA*搜索。估值函数就是目前拼图的曼哈顿距离。这是最理想化的情况,就是只经过曼哈顿距离,所有拼图回到自己位置。因为每次只能改变一个拼图的位置。所以最少就是曼哈顿距离。
估值函数的选取,就是去想最最理想的情况需要多少步达到目标。一般最理想都是实现不了,需要更多步数。所以最理想都超步数,那就更不用想其他方式了。
一开始一直tle,我把输入函数改了,就过了????gets比scanf快好多。
写搜索一定注意传递父亲的改变,进行父子剪枝。父亲向下儿子就不会向上,父亲增加儿子就别减少。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#define INF 0x3f3f3f3f
using namespace std;
int num[9];
char path[110],p[4]={'d','r','l','u'};
int xm[4]={1,0,0,-1},ym[4]={0,1,-1,0};
int check()//曼哈顿距离
{
int dis=0;
for(int i=0;i<9;i++)
{
if(num[i]==9) continue;
int x=i/3,y=i%3;
int xx=(num[i]-1)/3,yy=(num[i]-1)%3;
dis=dis+abs(xx-x)+abs(yy-y);
}
return dis;
}
bool astar(int loc,int de,int pre,int l) //IDA*搜索
{
int h=check();
if(de+h>l)
return false;
if(h==0)//找到解了
{
path[de]='\0';
printf("%s\n",path);
return true;
}
int x=loc/3,y=loc%3;
for(int i=0;i<4;i++)
{
if(i+pre==3) continue;//父子剪枝
int xx=x+xm[i],yy=y+ym[i];
if(xx<0||xx>2||yy<0||yy>2) continue;
int newloc=xx*3+yy;
swap(num[loc],num[newloc]);
path[de]=p[i];//记录移动
if(astar(newloc,de+1,i,l))
return true;
swap(num[loc],num[newloc]);
}
return false;
}
int main()
{
char s[50];
while(gets(s))
{
int cnt=0, loc;
for(int i=0;s[i];i++)
{
if(s[i]==' ')
continue;
if(s[i]=='x')
num[cnt] =9,loc=cnt++;
else
num[cnt++]=s[i]-'0';
}
int i;
for(i=0;i<=100;i++) //IDA*搜索
if(astar(loc,0,INF,i))
break;
if(i>100)
puts("unsolvable");
}
// char s[2];
// int loc;
// while(scanf("%s",s))
// {
// if(s[0]=='x')
// num[0]=9,loc=0;
// else
// num[0]=s[0]-'0';
// for(int i=1;i<=8;i++)
// {
// scanf("%s",s);
// if(s[0]=='x')
// num[i]=9,loc=i;
// else
// num[i]=s[0]-'0';
// }
// int i;
// for(i=1;i<=100;i++)
// if(astar(loc,0,-1,i))
// break;
// if(i>100)
// printf("unsolvable\n");
// }
}
一条蛇走出迷宫,不能走自身,不能走障碍物。
我的天呀,我hash就TLE,状态压缩就过,醉了。
对于这种需要保存动态障碍的,用状态压缩。本体的动态障碍都是头节点上下左右得到的,可以将头节点保存,再将头节点转换成障碍物的l-1次操作转化成4进制数。
这种迷宫的估值函数就是曼哈顿距离。
不要忘记起点就在障碍物上那种根本不能进行搜索的情况。
对于状态的标记,如果不想每次都memset,空间不大可以每次不判断是true还是false,而是每一次要清空的时候将填进去的数++,第一次填1,第二次填2这种,每次比较的是1,2,3.……
转换成进制数转移的时候一定要注意!
这个题一定要注意什么时候+xm,什么时候-xm!从头回溯的时候用加,那走下一步就是减,判断好加减代表的方向。
用进制还原的时候,千万不要用while(k),这样0的时候就不循环了,实际上0代表的是一大串状态!
#include <cstdio>
#include <queue>
using namespace std;
struct node
{
int x,y,h,s;
bool operator < (const node &b) const
{return s+x+y-2>b.s+b.x+b.y-2;}//曼哈顿距离
};
int vis[21][21][1<<14];//不用1<<16是因为,头坐标单独储存,l长身体,只用l-1个操作(l个相邻格子之间只有l-1个临边),最大不会超过4*4^6即1<<14
int n,m,l,len[8][2],map[21][21],body[21][21],bn,mn,xm[4]={0,1,0,-1},ym[4]={1,0,-1,0};//bn是这次第几条蛇的身体,mn是第几个输入地图,这样每次就不需要清零
int astar(node head)
{
priority_queue<node>q;
q.push(head);
while(!q.empty())
{
node tmp=q.top();
q.pop();
if(tmp.x==1&&tmp.y==1)
return tmp.s;
bn++;//第几条蛇的身体
int x=tmp.x,y=tmp.y,h=tmp.h;
for(int i=l-1;i;i--)//千万不要用while(h)
{
x+=xm[h%4],y+=ym[h%4];
body[x][y]=bn;
h/=4;
}
for(int i=0;i<4;i++)
{
x=tmp.x-xm[i],y=tmp.y-ym[i];//这里是减
if(x<1||y<1||x>n||y>m) continue;
if(map[x][y]==mn||body[x][y]==bn) continue;
node now;
now.x=x;
now.y=y;
now.h=tmp.h%(1<<(2*(l-2)));
now.h=now.h*4+i;
if(vis[now.x][now.y][now.h]!=mn)
{
vis[now.x][now.y][now.h]=mn;
now.s=tmp.s+1;
q.push(now);
}
}
}
return -1;
}
int main()
{
bn=mn=0;
int k,x,y;
while(scanf("%d%d%d",&n,&m,&l)&&n)
{
node tmp;
tmp.s=0;
mn++;//mn在此处++
for(int i=0;i<l;i++)
scanf("%d%d",&len[i][0],&len[i][1]);//注意要从后往头hash,所以先存起来
scanf("%d",&k);
for(int i=0;i<k;i++)
{
scanf("%d%d",&x,&y);
map[x][y]=mn;
}
tmp.h=0;
tmp.x=len[0][0],tmp.y=len[0][1];
for(int i=l-1;i;i--)
{
for(int j=0;j<4;j++)
if(len[i][0]-len[i-1][0]==xm[j]&&len[i][1]-len[i-1][1]==ym[j])
tmp.h=tmp.h*4+j;
}
printf("Case %d: ",mn);
vis[tmp.x][tmp.y][tmp.h]=mn;
if(map[tmp.x][t