1.题目描述:点击打开链接
2.解题思路:本题是一道很经典的路径寻找问题,利用A*算法解决。即BFS+利用估价函数剪枝。做本题的第一个障碍是题意比较复杂,需要事先在草稿纸上理清头绪,弄清楚这4种操作,每种操作又分哪些特殊情况。还要弄清楚4种冰块是怎么转换的。这样,弄清楚这些细节,才能够写出鲁棒的扩展状态的函数。
接下来,就是如何利用BFS来寻找路径了。首先要弄清楚怎么保存一个状态,应该记录哪些我们关心的量。在本题中,由于输入的是一个字符串,只要将他们排成一列,即可完整的描述出当前的一个状态,因此我们用字符串的形式来保存一个状态。由于最终要的不是最小的步数,而是一系列操作,因此这里的d数组保存的应该是实现最短步数的一串操作,即字符串->字符串(普通的BFS是“状态”->整数)。这里我们不再用d数组,改用sol(solution的缩写)来存储,还可以知道sol应该是一个map类型。BFS还需要一个queue来实现,由于状态是字符串,即queue<string>q。
接下来就是确定BFS的主体框架,这点其实也不难,为了更清楚的表示状态的扩展,我们可以专门写一个expand函数来实现,这样,大致框架应该如下:
while(!q.empty()) { string s=q.front(); q.pop(); if(expand(s,'<'))break; if(expand(s,'>'))break; if(expand(s,'L'))break; if(expand(s,'R'))break; }
if(!sol.count(s)) { sol[s]=seq; q.push(s); }
#define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<algorithm> #include<string> #include<sstream> #include<set> #include<vector> #include<stack> #include<map> #include<queue> #include<deque> #include<cstdlib> #include<cstdio> #include<cstring> #include<cmath> #include<ctime> #include<cctype> #include<functional> using namespace std; #define me(s) memset(s,0,sizeof(s)) #define pb push_back typedef long long ll; typedef unsigned int uint; typedef unsigned long long ull; typedef pair <int, int> P; const int N=256; int n,m,target; //行数,列数和目标格的编号(从上到下编号为0~nm-1) map<string,string>sol; //sol[s]表示从初始状态到达状态s的最短操作序列 queue<string>q; //BFS用的状态队列 bool icy[N]; char link_l[N],link_r[N],clear_l[N],clear_r[N]; void init()//初始化常量数组 { memset(icy,0,sizeof(icy));//icy[i]表示字符i是否为冰 icy['O']=icy['[']=icy[']']=icy['=']=true; memset(link_l,' ',sizeof(link_l)); //link_l[c]表示将字符c往左连接后的新字符 link_l['O']=']';link_l['[']='='; memset(link_r,' ',sizeof(link_r)); //link_r[c]表示将字符c往右连接后的新字符 link_r['O']='[';link_r[']']='='; memset(clear_l,' ',sizeof(clear_l)); //clear_l[c]表示将c左端的字符清除后的新字符 clear_l[']']='O';clear_l['=']='[';clear_l['O']='O';clear_l['[']='['; memset(clear_r,' ',sizeof(clear_r)); //clear_r[c]表示将c右端的字符清除后的新字符 clear_r['[']='O';clear_r['=']=']';clear_r['O']='O';clear_r[']']=']'; } string fall(string s)//让状态中的悬空的冰块和冰人落地 { int k,r,p; for(int i=n-1;i>=0;i--) //由于是落地操作,要从下往上寻找 for(int j=0;j<m;j++)//从左往右寻找 { char ch=s[i*m+j]; if(ch=='O'||ch=='@')//如果是独立的冰块或冰人 { for(k=i+1;k<n;k++)if(s[k*m+j]!='.')break; s[i*m+j]='.';s[(k-1)*m+j]=ch; } else if(ch=='[')//“冰棍”的左端 { for(r=j+1;r<m;r++)if(s[i*m+r]=='X'||s[i*m+r]==']')break; //寻找冰棍的右端,试图得到冰棍[j...r] if(s[i*m+r]==']') //是一个完整的“冰棍”,可以下落,如果连接的右端是石头就不能下落 { for(k=i+1;k<n;k++) //在i+1,i+2,...,n-1行寻找支撑点 { bool found=false; for(p=j;p<=r;p++)if(s[k*m+p]!='.'){found=true;break;} if(found)break;//找到了支撑点(k,p) } for(p=j;p<=r;p++)s[i*m+p]='.'; //冰棍原来的所有地方都变为空格 for(p=j+1;p<r;p++)s[(k-1)*m+p]='='; //下落后的位置,冰棍中间部分都是无自由端的冰 s[(k-1)*m+j]='[';s[(k-1)*m+r]=']'; //确定左右两端冰的类型 } j=r; //j直接跳到冰棍的右端,开始新的寻找 } } return s; //返回下落后的新状态 } int h(string s)//估价函数,确定从当前状态s到达目标点还有至少多少步 { int a,b,x=s.find('@'); a=x%m-target%m; if(a<0)a=-a; //确定横向距离a if(x/m>target/m)b=x/m-target/m; //如果目标点在高处,那么至少还有走b步 else b=(x/m<target/m?1:0);//如果等高,b=0, //如果目标点在低处,最快就是一步跳下去,b=1 return a>b?a:b; //返回较大者 } bool expand(string s,char cmd)//扩展出当前状态s执行cmd后的新状态,如果找到了解,返回true { string seq=sol[s]+cmd;//新状态的操作序列,等价于经典的BFS中的最短步数 int x=s.find('@'); //找到冰人的位置 s[x]='.'; //暂时视为冰人离开了原地 if(cmd=='<'||cmd=='>') //施魔法操作 { s[x]='@'; int p=(cmd=='<'?x+m-1:x+m+1);//魔法作用的障碍格子的编号 if(s[p]=='X')return false; //不能对石头施法 else if(s[p]=='.') //目标是空地,可以变成冰 { s[p]='O'; if(icy[s[p-1]])s[p-1]=link_r[s[p-1]];//如果p左端是冰,那么p-1处的冰块向右连接 if(s[p-1]!='.')s[p]=link_l[s[p]]; //如果p左边是冰或者石头,p向左连接 if(icy[s[p+1]])s[p+1]=link_l[s[p+1]];//同上 if(s[p+1]!='.')s[p]=link_r[s[p]]; //同上 } else //目标是冰,可以变为空地 { s[p]='.'; if(icy[s[p-1]])s[p-1]=clear_r[s[p-1]]; //如果p左边是冰,拆除p-1处冰块向右的连接 if(icy[s[p+1]])s[p+1]=clear_l[s[p+1]]; //同上 } } else //移动操作 { int p=(cmd=='L'?x-1:x+1); //移动目标 if(s[p]=='.')s[p]='@'; //目标是空地,直接走过去 else { if(s[p]=='O') //目标地是独立冰,尝试把它推走,注意:此时冰人原地不动 { int k; if(cmd=='L'&&s[p-1]=='.')//往左推 { for(k=p-1;k>0;k--)if(s[k-1]!='.'||s[k+m]=='.')break; s[p]='.';s[k]='O';s[x]='@'; } if(cmd=='R'&&s[p+1]=='.')//往后推 { for(k=p+1;k<n*m;k++)if(s[k+1]!='.'||s[k+m]=='.')break; s[p]='.';s[k]='O';s[x]='@'; } } if(s[p]!='.') //遇到障碍,或者独立冰没有被推走,往上爬 { if(s[p-m]=='.'&&s[x-m]=='.')s[p-m]='@'; else s[x]='@'; } } } s=fall(s); //悬空冰块和冰人往下落 if(h(s)+seq.length()>15)return false; //最优性剪枝 if(s.find('@')==target) //找到解 { printf("%s\n",seq.c_str()); return true; } if(!sol.count(s)) //判重操作 { sol[s]=seq; q.push(s); } return false; } int main() { int rnd=0; init(); //初始化常量数组 while(~scanf("%d%d",&n,&m)) { if(!n)break; char mp[20][20]; for(int i=0;i<n;i++) scanf("%s",mp[i]); string s=""; for(int i=0;i<n;i++) for(int j=0;j<m;j++) { if(mp[i][j]=='#') { target=i*m+j; mp[i][j]='.'; } s+=mp[i][j]; } q.push(s); sol.clear(); sol[s]=""; printf("Case %d: ",++rnd); while(!q.empty()) { string s=q.front(); q.pop(); if(expand(s,'<'))break; if(expand(s,'>'))break; if(expand(s,'L'))break; if(expand(s,'R'))break; } while(!q.empty())q.pop(); } }