uva12112 - Iceman BFS模拟 启发式搜索

Iceman was in a room in Iceland. He wants to get out of it, but it’s not aseasy as it seems. To help him, you need to know something about Icelandand Iceman.The room could be described by an n × m grid, so there are n rowsnumbered 1 to n from top to bottom, each with m squares numbered 1 tom from left to right. Each square may be empty, icy or rocky. An emptysquare is denoted by ‘.’, while a rocky square is denoted by ‘X’. Ice squaresare a bit complex, so we talk about it later. The first and last rows andcolumns are all rocky. The iceman’s initial location is always an emptysquare at the beginning, denoted by ‘@’. His destination is also emptyat the beginning, denoted by ‘#’. What’s more, the destination is alwaysdirectly above a rocky square. Though the iceman looks bigger than asquare, he always occupies exactly one single empty square.The iceman has four kinds of moves: go left (L), go right (R), magic wind left (<) and magic windright (>). Suppose the iceman is at (r, c), then ‘<’ move operates on the iceman’s bottom-left square(r + 1, c−1). If the square is rocky, the move does nothing; if the square is empty, it becomes icy; if thesquare is icy, it becomes empty. There are four kinds of icy squares: ice with two free-ends (O), ice withleft free-end ([), ice with right free-end (]), ice with no free-end (=). Here ‘free’ means ‘not connected’.If an icy square is created by a magic-wind move, it connects to its left/right neighbor, provided thecorresponding neighboring square is not empty (rocky or icy). The connections between neighboringsquares are symmetric, so squares that are connected to each other behave as a whole. Connectionscould be created only by magic moves, and there are no vertical connections. We call the whole an icebar. If an icy bar (no matter how many squares does it contain) has two free ends, we call it a freeice bar. Free ice bars immediate drop down vertically when all its supporting squares (squares directlybelow it) are empty. When an icy square is cleared, all connections of it (if any) are destroyed. Becauserocky squares are fixed, if an ice bar connects to one or two rocky squares, it never drops down untilits connections to rocky squares are all destroyed. The ‘>’ move is symmetrical.The ‘L’ move is a little bit complex, compared to what you might expect. Again, suppose theiceman is located at (r, c). If (r, c − 1) is an empty square, the iceman go there. Now he stands on thesquare (r + 1, c − 1), which might be empty. If this is true, he falls down until the square under him isnot empty. The iceman can launch a move only when he’s standing on a rocky or icy square, so whenfalling down, he cannot do anything. Now consider the second case, i.e. (r, c − 1) is rocky. Obviouslythe iceman cannot move to that square, so he checks the square above it and the square above himself(i.e the squares (r − 1, c − 1) and (r − 1, c)). If both are empty, he climbs to (r − 1, c − 1), otherwise heremains at (r, c). The third and last case holds when (r, c − 1) is icy. In this case, the iceman tries topush it. The Iceman is not so powerful, so he can only push single (1 × 1) icy squares. That is, if onlyif (r, c − 2) is empty, the ice at (r, c − 1) is pushed left. It continues to move left until it is blocked bya rocky or icy square, or the square directly below is empty. In the latter case, the ice drops down, asstated before. Note that when stopped dropping, the ice does not move left again. Don’t forget thatwhen the ice is pushed away successfully, some free ice bars may drop down. The iceman does not moveuntil everything stopped moving or dropping. If the ice at (r, c − 1) cannot be pushed, it is treated asa rocky square, so the iceman may climb on it.Write a program to move the iceman to the destination with minimum number of moves.InputThe input consists of several test cases. The first line of each case contains two integers, n and m(1 ≤ n, m ≤ 10). This is followed by n lines, each containing m characters. Each character is one of‘.’, ‘X’, ‘@’, ‘#’, ‘O’, ‘[’, ‘]’, ‘=’. The last test case is followed by a single zero, which should not beprocessed.OutputFor each test case, print the case number and the move sequence. There is always a solution of at most15 moves. It is guaranteed that the optimal solution is unique.Sample Input55XXXXXX@.#XXX.XXX...XXXXXX77XXXXXXXX.....XX@[=].XXXX.XXXXXX.XXXXXX#[email protected]=XX..O.XX.#O.XXXXXXX0Sample OutputCase 1: >RRCase 2: R>RCase 3: RR>RLLLLL>R

  据说是根据一个FC游戏所罗门之匙改编,虽然没玩过,但红白机游戏玩的不少,所以能想象出大概是什么样子。题意很复杂,但是能模拟出游戏的过程,还是非常有意思的。

  把整个画面想成竖着的,有石头,有冰块,冰人站在一个地方,要从起点到终点。连起来的冰块组成冰棒,悬空的冰人和冰棒会落下去。冰人可以移动或者往上爬,可以使用魔法把他左下方或右下方的冰块变没,或着从空的变成冰块,也可以推动旁边的冰块。

  题目问的是从起点到终点的最少操作,整体思路是BFS模拟,题目告诉你了不超过15步,加一个启发函数优化。

  照书上抄了一遍=。=

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
#include<map>
#define INF 0x3f3f3f3f
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

typedef long long LL;

const int MAXN=500010;

int N,M;
int target;                 //目标位置编号
bool icy[256];              //是否是冰
char a[20][20];             //输入矩阵
char link_l[256], link_r[256], clear_l[256], clear_r[256];      //link是左右连接后的新字符,clear是拆除左右连接后的新字符
map<string,string> sol;     //sol[s]表示从初始状态到s的最短操作序列
queue<string> q;            //BFS状态队列

void init(){
    memset(icy,0,sizeof(icy));
    icy['O']=icy['[']=icy[']']=icy['=']=true;
    memset(link_l,' ',sizeof(link_l));
    link_l['O']=']';
    link_l['[']='=';
    memset(link_r,' ',sizeof(link_r));
    link_r['O']='[';
    link_r[']']='=';
    memset(clear_l,' ',sizeof(clear_l));
    clear_l[']']='O';
    clear_l['=']='[';
    clear_l['[']='[';
    clear_l['O']='O';
    memset(clear_r,' ',sizeof(clear_r));
    clear_r['[']='O';
    clear_r['=']=']';
    clear_r[']']=']';
    clear_r['O']='O';
}

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;
                }
                if(s[i*M+r]==']'){
                    for(k=i+1;k<N;k++){                 //依次检查i+1,...,N-1行是否有东西支撑
                        bool found=false;
                        for(p=j;p<=r;p++) if(s[k*M+p]!='.'){
                            found=true;
                            break;
                        }
                        if(found) break;
                    }
                    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;
            }
        }
    return s;
}


//启发函数。至少需要h(s)次才能到达目标位置
int h(string s){
    int a,b,x=s.find('@');
    a=abs(x%M-target%M);                                //横向距离为a,至少要移动a次
    if(x/M>target/M) b=x/M-target/M;                    //目标位置比较高,一次最多往上走一格
    else b=(x/M<target/M?1:0);                          //目标位置低
    return max(a,b);
}

//扩展出状态s进行cmd操作后的新状态,找到解返回true
bool expand(string s,char cmd){
    string seq=sol[s]+cmd;                              //新状态的操作序列
    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左边是冰块,向右连接
            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]];      //如果p右边是冰块,向左连接
            if(s[p+1]!='.') s[p]=link_r[s[p]];          //如果p右边有东西,p向右连接
        }
        else{                                           //把冰变成空格
            s[p]='.';
            if(icy[s[p-1]]) s[p-1]=clear_r[s[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(){
    freopen("in.txt","r",stdin);
    int cas=0;
    init();
    while(scanf("%d%d",&N,&M)!=EOF&&N){
        while(!q.empty()) q.pop();
        for(int i=0;i<N;i++) scanf("%s",a[i]);
        string s="";
        for(int i=0;i<N;i++)
            for(int j=0;j<M;j++){
                if(a[i][j]=='#'){
                    target=i*M+j;
                    a[i][j]='.';
                }
                s+=a[i][j];
            }
        q.push(s);
        sol.clear();
        sol[s]="";
        printf("Case %d: ", ++cas);
        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;
        }
    }
    return 0;
}


你可能感兴趣的:(uva12112 - Iceman BFS模拟 启发式搜索)