hihocoder 1828 Saving Tang Monk II (DP+BFS)

题目链接

Problem Description
《Journey to the West》(also 《Monkey》) is one of the Four Great Classical Novels of Chinese literature. It was written by Wu Cheng'en during the Ming Dynasty. In this novel, Monkey King Sun Wukong, pig Zhu Bajie and Sha Wujing, escorted Tang Monk to India to get sacred Buddhism texts.

During the journey, Tang Monk was often captured by demons. Most of demons wanted to eat Tang Monk to achieve immortality, but some female demons just wanted to marry him because he was handsome. So, fighting demons and saving Monk Tang is the major job for Sun Wukong to do.

Once, Tang Monk was captured by the demon White Bones. White Bones lived in a palace and she cuffed Tang Monk in a room. Sun Wukong managed to get into the palace, and he wanted to reach Tang Monk and rescue him.

The palace can be described as a matrix of characters. Different characters stand for different rooms as below:

'S' : The original position of Sun Wukong

'T' : The location of Tang Monk

'.' : An empty room

'#' : A deadly gas room.

'B' : A room with unlimited number of oxygen bottles. Every time Sun Wukong entered a 'B' room from other rooms, he would get an oxygen bottle. But staying there would not get Sun Wukong more oxygen bottles. Sun Wukong could carry at most 5 oxygen bottles at the same time.

'P' : A room with unlimited number of speed-up pills. Every time Sun Wukong entered a 'P' room from other rooms, he would get a speed-up pill. But staying there would not get Sun Wukong more speed-up pills. Sun Wukong could bring unlimited number of speed-up pills with him.

Sun Wukong could move in the palace. For each move, Sun Wukong might go to the adjacent rooms in 4 directions(north, west,south and east). But Sun Wukong couldn't get into a '#' room(deadly gas room) without an oxygen bottle. Entering a '#' room each time would cost Sun Wukong one oxygen bottle.

Each move took Sun Wukong one minute. But if Sun Wukong ate a speed-up pill, he could make next move without spending any time. In other words, each speed-up pill could save Sun Wukong one minute. And if Sun Wukong went into a '#' room, he had to stay there for one extra minute to recover his health.

Since Sun Wukong was an impatient monkey, he wanted to save Tang Monk as soon as possible. Please figure out the minimum time Sun Wukong needed to reach Tang Monk.
Input
There are no more than 25 test cases.

For each case, the first line includes two integers N and M(0 < N,M ≤ 100), meaning that the palace is a N × M matrix.

Then the N×M matrix follows.

The input ends with N = 0 and M = 0.
Output
For each test case, print the minimum time (in minute) Sun Wukong needed to save Tang Monk. If it's impossible for Sun Wukong to complete the mission, print -1
Sample Input
2 2
S#
#T
2 5
SB###
##P#T
4 7
SP.....
P#.....
......#
B...##T
0 0
Sample Output
-1
8
11
题意
给一个100x100的迷宫,'.'表示路面,'S'表示起点,'T'表示终点;'#'表示毒气区,进入毒气区必须要消耗一个氧气;'B'表示氧气区,每次进入自动获得一个氧气,可反复进入从而获得多个,但最多携带5个;'P'表示加速药,获得原理和氧气一样,使用后使下一次移动不耗时,可以无限携带。一次移动可以移动到相邻的四个格子,花费一个单位时间,如果移动到了毒气区,将在毒气区额外停留一个单位时间。求从S到T的最短时间,如果不能到达,输出-1。
分析
加速药拿到立刻用掉和留着后面用的效果是一样的,所以完全不必考虑加速药的存在。
氧气数量是这道题的关键,所以把状态定义为(x, y, n),表示在(x,y)时还有n个氧气,当氧气用完时,就不能向毒气区转移了;同时我们还希望求出的最短时间,所以dp[x][y][n]=t,表示从起点出发到达状态(x, y, n)花费的最少时间,这样以后,如果终点是(tx,ty),那么只要dp[tx][ty][i],(i=0,1,2,3,4,5)中任何一个不是无穷大,就是可以到达终点的。
接下来是状态之间的转移:
(x, y, n)可以向四个方向转移,假设下一个地方是(tx,ty),那么:
(tx,ty)是'.'或'S',就用dp[x][y][n]+1更新dp[tx][ty][n];
(tx,ty)是'T',同样用dp[x][y][n]+1更新dp[tx][ty][n],并且不再向下转移;
(tx,ty)是'B',氧气数量增加,如果n<5,那么还可以拿氧气,用dp[x][y][n]+1更新dp[tx][ty][n+1],否则更新dp[tx][ty][n];
(tx,ty)是'P',下一步不耗时,用dp[x][y][n]更新dp[tx][ty][n];
(tx,ty)是'#',氧气数量减少,只有当n>0时,才可以进毒气区,因为要额外花费一个单位时间,用dp[x][y][n]+2更新dp[tx][ty][n-1]。
那么所有的转移都搞定了,初始状态很简单,假设起点是(sx,sy),那么就是dp[sx][sy][0]=0,其他所有的状态都是INF。只要把所有可能到达的状态更新了,那么答案就在dp[tx][ty][i]中取最小就行了。
需要注意的是,状态的更新需要用类似于BFS的顺序,不断的用已知的最优状态,去更新相邻的未知状态,直至遍历完所有的状态,复杂度为O(5nm)。
总结
网格题真的是一点想法也没有,看数据量也不大,就试着写了个状态推一推,没想到真的推出来了。经大佬指点用spfa也可以搞,等学会了再补题把。
代码
#include<stdio.h>
#include<memory.h>
#include<queue>
using std::queue;
char g[105][105];
int dp[105][105][10];//从起点出发直到在(x,y)拿着n个氧气罐所需最少时间
#define INF 1000000
int nx[] = { -1,0,1,  0 };
int ny[] = {   0,1,0,-1 };
struct state
{
    int x, y, n;
    state(int _i=0,int _j=0,int _x=0):x(_i),y(_j),n(_x){}
};

int main()
{
    int n, m;
    int sx=0, sy=0, ex=0, ey=0;
    while(EOF!=scanf("%d %d",&n,&m)&&n&&m){
        for(int i=1;i<=n;++i){
            scanf("%s", g[i] + 1);
            g[i][0] = '$';
            for(int j=1;j<=m;++j){
                if (g[i][j] == 'S') { sx = i; sy = j; }
                else if (g[i][j] == 'T') { ex = i; ey = j; }
                //输入时顺带初始化dp数组
                for (int t = 0; t <= 5; ++t)dp[i][j][t] = INF;
            }
        }
        //起始状态
        dp[sx][sy][0] = 0;
        //bfs的顺序更新
        queue<state> mq;
        mq.push(state(sx, sy, 0));
        state cur;
        int tx, ty;
        while(!mq.empty())    {
            cur = mq.front(); mq.pop();
            for(int k=0;k<4;++k)//四个转移方向
            {
                tx = cur.x + nx[k];
                ty = cur.y + ny[k];
                //不在地图内
                if (tx<1 || tx>n || ty<1 || ty>m)continue;

                switch(g[tx][ty]){
                    case 'S':
                    case '.':
                        if (dp[tx][ty][cur.n] > dp[cur.x][cur.y][cur.n] + 1){
                            dp[tx][ty][cur.n] = dp[cur.x][cur.y][cur.n] + 1;
                            mq.push(state(tx, ty, cur.n));
                        }
                        break;
                    case 'T':
                        //不再向下转移
                        if (dp[tx][ty][cur.n] > dp[cur.x][cur.y][cur.n] + 1)
                            dp[tx][ty][cur.n] = dp[cur.x][cur.y][cur.n] + 1;
                        break;
                    case 'P':
                        if (dp[tx][ty][cur.n] > dp[cur.x][cur.y][cur.n]){
                            dp[tx][ty][cur.n] = dp[cur.x][cur.y][cur.n];
                            mq.push(state(tx, ty, cur.n));
                        }
                        break;
                    case 'B':
                        if (cur.n<5&&dp[tx][ty][cur.n + 1] > dp[cur.x][cur.y][cur.n] + 1){
                            dp[tx][ty][cur.n + 1] = dp[cur.x][cur.y][cur.n] + 1;
                            mq.push(state(tx, ty, cur.n +1));
                        }
                        else if(cur.n==5&&dp[tx][ty][cur.n] > dp[cur.x][cur.y][cur.n] + 1){
                            dp[tx][ty][cur.n] = dp[cur.x][cur.y][cur.n] + 1;
                            mq.push(state(tx, ty, cur.n));
                        }
                        break;
                    case '#':
                        if (cur.n>0&&dp[tx][ty][cur.n - 1] > dp[cur.x][cur.y][cur.n] + 2){
                            dp[tx][ty][cur.n - 1] = dp[cur.x][cur.y][cur.n] + 2;
                            mq.push(state(tx, ty, cur.n -1));
                        }
                        break;
                    default:
                        break;
                    }

            }
        }
        int ans = INF;
        for(int i=0;i<=5;++i){
            if (ans > dp[ex][ey][i])ans = dp[ex][ey][i];
        }
        if (ans >= INF)printf("-1\n");
        else printf("%d\n", ans);
    }
}
posted @ 2018-09-22 22:08  会打表的toby  阅读(374)  评论(0编辑  收藏  举报