迭代加深和A*都是常用的搜索方法,当它们融汇贯通的时候,会发生很奇妙的事情……
题意
有些格子上有墙,可以推墙到相邻格子上(前提是相邻格子的位置上没有墙),求走出迷宫的一条可行道路。
分析
由于要不断地推墙,所以可以走重复格子,这样就会无休止地走下去,所以我们只能用迭代加深,限定每次深搜走几步了。
然后是寻路的话自然可以用A*搞一搞,每次判断当前格子到最近出口的直线距离,如果当前步数+预计最短距离>限定步数的话,可以剪个枝。
最后随便搞一搞就AC了。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<climits>
#include<iomanip>
#include<algorithm>
#include<queue>
using namespace std;
int read(){
int q=0;char ch=' ';
while(ch<'0'||ch>'9')ch=getchar();
while(ch>='0'&&ch<='9')q=q*10+ch-'0',ch=getchar();
return q;
}
int sx,sy,d,tot;
//W:1,N:2,E:4,S:8
int ma[5][7];
int mvx[5]={0,0,0,1,-1},mvy[5]={0,1,-1,0,0},wal[5]={0,4,1,8,2};
int op[5]={0,1,4,2,8};
char s[5]={' ','E','W','S','N'};
char ans[1005];
int h(int x,int y){
int i,re=INT_MAX;
for(i=1;i<=4;i++){
if(!(ma[i][1]&wal[2]))re=min(re,abs(x-i)+abs(y-1));
if(!(ma[i][6]&wal[1]))re=min(re,abs(x-i)+abs(y-6));
}
for(i=1;i<=6;i++){
if(!(ma[1][i]&wal[4]))re=min(re,abs(x-1)+abs(y-i));
if(!(ma[4][i]&wal[3]))re=min(re,abs(x-4)+abs(y-i));
}
return re;
}
bool dfs(int x,int y,int dep){
if(h(x,y)+dep>d)return 0;
if(dep==d){
if((x==1||x==4||y==1||y==6)&&!h(x,y)){
if(x==1&&!(ma[x][y]&wal[4]))ans[++tot]=s[4];
else if(x==4&&!(ma[x][y]&wal[3]))ans[++tot]=s[3];
else if(y==1&&!(ma[x][y]&wal[2]))ans[++tot]=s[2];
else ans[++tot]=s[1];
return 1;
}
return 0;
}
for(int i=1;i<=4;i++){
int tx=x+mvx[i],ty=y+mvy[i];
if(tx<1||ty<1||tx>4||ty>6)continue;
if(!(ma[x][y]&wal[i]))//没墙
{if(dfs(tx,ty,dep+1)){ans[++tot]=s[i];return 1;}}
else {//推墙
int ttx=tx+mvx[i],tty=ty+mvy[i];
if(ma[tx][ty]&wal[i])continue;
ma[x][y]-=wal[i];ma[tx][ty]+=wal[i];ma[tx][ty]-=op[i];
if(ttx>=1&&ttx<=4&&tty>=1&&tty<=6)ma[ttx][tty]+=op[i];
if(dfs(tx,ty,dep+1)){ans[++tot]=s[i];return 1;}
ma[x][y]+=wal[i];ma[tx][ty]-=wal[i];ma[tx][ty]+=op[i];
if(ttx>=1&&ttx<=4&&tty>=1&&tty<=6)ma[ttx][tty]-=op[i];
}
}
return 0;
}
int main()
{
int i,j;
while(1){
sy=read();sx=read();
if(!sx&&!sy)break;
for(i=1;i<=4;i++)
for(j=1;j<=6;j++)ma[i][j]=read();
for(d=0;;d++){tot=0;if(dfs(sx,sy,0))break;}//从0开始
for(i=tot;i>=1;i--)printf("%c",ans[i]);
printf("\n");
}
return 0;
}
拓展练习题
codevs2495水叮当的舞步。
中文题就不题意了…..
广搜没办法判重,肯定会爆空间。深搜又难剪枝,绝对会爆时间,这道题我用广搜做了两个多小时,只拿了10分,血的教训啊……
这种时候就要考虑迭代加深和A*的优化了,迭代加深可以缓解广搜空间的问题,而A*则可以成为深搜的极好优化手段。我们先想想迭代加深,那么可以规定最多跳多少步。而在当前状态下最优解是剩余颜色的数量,所以我们考虑用剩余颜色作为A*的估价函数。
还有一个优化图,就是将左上角联通色块变成“1”,它旁边的都变成“2”,剩下的变成“0”,这样方便我们写估价函数和寻找可以扩展的色块。
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<climits>
#include<cstdlib>
#include<iomanip>
#include<algorithm>
using namespace std;//颜色是0到5
int mvx[5]={0,1,-1,0,0},mvy[5]={0,0,0,1,-1};
int ma[9][9],ok[9][9];//ma是毯子,ok是一个优化图
int dlx[65],dly[65];
bool v[6];
int m,n,ans;
int gj(int color){//估价函数,处理剩余颜色数量
memset(v,0,sizeof(v));
int i,j,sum=0;
for(i=1;i<=n;i++)
for(j=1;j<=n;j++){
if(ok[i][j]!=1&&v[ma[i][j]]==0){
v[ma[i][j]]=1;sum++;
}
}
return sum;
}
void bfs(int sx,int sy,int color){//采用广搜寻找扩展的状态
int i,j,k,tx,ty;
int head=1,tail=1;
dlx[1]=sx;dly[1]=sy;ok[sx][sy]=1;
while(head<=tail){
for(i=1;i<=4;i++){
tx=mvx[i]+dlx[head];
ty=mvy[i]+dly[head];
if(tx>=1&&tx<=n&&ty>=1&&ty<=n&&ok[tx][ty]==0){
if(ma[tx][ty]==color){
ok[tx][ty]=1;
tail++;dlx[tail]=tx;dly[tail]=ty;
}
else {ok[tx][ty]=2;}//在联通块旁边
}
}
head++;
}
}
bool dfs(int d,int f,int now,int color){
int i,j,k;
int bj=0;
if(now+f>d){return 0;}//如果当前解+剩余最优解>规定层数,剪枝
if(f==0){return 1;}//如果没有其它色块了,就找到了解。
int bf[9][9];//注意,由于搜到下层会改变bf的值,所以bf要在函数里定义
for(i=0;i<=5;i++){
bj=0;
if(i==color)continue;//如果i=color相当于不染色
for(j=1;j<=n;j++)
for(k=1;k<=n;k++)bf[j][k]=ok[j][k];//bf数组备份一个ok
for(j=1;j<=n;j++)
for(k=1;k<=n;k++){
if(ok[j][k]==2&&ma[j][k]==i){
bj=1;bfs(j,k,i);
}
}
if(bj==1){
if(dfs(d,gj(i),now+1,i)==1){return 1;}
for(j=1;j<=n;j++)
for(k=1;k<=n;k++)ok[j][k]=bf[j][k];
}
}
for(j=1;j<=n;j++)//将ok变回来
for(k=1;k<=n;k++)ok[j][k]=bf[j][k];
return 0;
}
int main()
{
int i,j,k;
while(1){
scanf("%d",&n);
memset(ok,0,sizeof(ok));
if(n==0)break;
for(i=1;i<=n;i++)
for(j=1;j<=n;j++){
scanf("%d",&ma[i][j]);
}
bfs(1,1,ma[1][1]);//确认左上角联通块
for(ans=0;ans<=n*n;ans++){//一定要从0开始,不然一开始就是所有色块一样的毯子没有答案
if(dfs(ans,gj(ma[1][1]),0,ma[1][1])==1){
printf("%d\n",ans);
break;
}
}
}
return 0;
}