美文网首页
PROB: maze1

PROB: maze1

作者: SylviaShen | 来源:发表于2017-09-07 15:31 被阅读0次

    题目来自 USACO
    题目翻译见 NOCOW

    思路

    给地图,含两个出口,标准的最短路径问题。之前我在 codingames 某周的问题里也见过几乎同样的题,不过当时还不会写。

    AC代码: 用 dijkstra

    我先用 dijkstra 算法算了一下。里面遇到几个坑,调了好久才跑对。

    • 输入的时候不能像上一个问题一样做一层循环,然后每行读 %s 进数组了,因为这个行里面有大量空格,%s 会自动结束的。因此我只会两重循环读 %c了。这里又两个坑,首先前面两个数字之后有个 \n 要读掉,还有每行结束的 \n 都要手动读掉。
    • 寻找两个出口,我对最东、西、南、北的都检查了一遍。写到后来我又把读到的出口顺带换个字符堵上了,这样出口这个点可以和其它点一样,检查是否有四面墙即可,不用任何特殊处理。

    我也不知道代码为什么会这么这么又丑又长,可能是这个地图被迫满篇写两重循环吧…待会我用可爱的 spfa 再做一下,也顺带看看题解有没有什么好写法不这么难看。

    /*
    ID:
    LANG: C++
    TASK: maze1
    */
    
    #include <cstdio>
    #include <cstdlib>
    
    #define hasNorthWall(x, y) (map[2 * (x) - 2][2 * (y) - 1] == '-')
    #define hasEastWall(x, y) (map[2 * (x) - 1][2 * (y)] == '|')
    #define hasSouthWall(x, y) (map[2 * (x)][2 * (y) - 1] == '-')
    #define hasWestWall(x, y) (map[2 * (x) - 1][2 * (y) - 2] == '|')
    //#define valid(x, y) (x >= 1 && x <= H && y >= 1 && y <= W)
    #define min(x, y) ((x) < (y)? (x): (y))
    
    const int maxW = 38, maxH = 100;
    int W, H;
    char map[2 * maxH + 1][2 * maxW + 1], temp; //地图
    int exitX[2], exitY[2]; //两个出口
    int minDist[2][maxH + 1][maxW + 1]; //对两个出口的最短距离
    
    void dijkstra(int exit){
        bool visited[maxH + 1][maxW + 1] = {0}; //最短距离是否确定
        minDist[exit][exitX[exit]][exitY[exit]] = 1; //设出口的距离为1(往外走一步)
        int i, j, x, y, d;
        while(1){
            //找现在unvisited的距离最短的
            d = 40000;
            for(i = 1; i <= H; i ++){
                for(j = 1; j <= W; j ++){
                    if(!visited[i][j] && minDist[exit][i][j] < d){
                        d = minDist[exit][i][j];
                        x = i, y = j;
                    }
                }
            }
            //都已经visit了,退出
            if(d == 40000) break; 
            //加入visited
            visited[x][y] = true;
            //更新邻居
            if(!hasNorthWall(x, y)) minDist[exit][x - 1][y] = min(minDist[exit][x - 1][y], d + 1);
            if(!hasEastWall(x, y)) minDist[exit][x][y + 1] = min(minDist[exit][x][y + 1], d + 1);
            if(!hasSouthWall(x, y)) minDist[exit][x + 1][y] = min(minDist[exit][x + 1][y], d + 1);
            if(!hasWestWall(x, y)) minDist[exit][x][y - 1] = min(minDist[exit][x][y - 1], d + 1);
        }
    }
    
    int main(){
        FILE *fin = fopen("maze1.in", "r");
        FILE *fout = fopen("maze1.out", "w");
    
        fscanf(fin, "%d %d\n", &W, &H); //下面用%c读取,这里必须\n也过掉
        for(int i = 0; i < 2 * H + 1; i ++){
            for(int j = 0; j < 2 * W + 1; j ++){
                fscanf(fin, "%c", &(map[i][j]));
            }
            fscanf(fin, "%c", &temp);//行末\n要过掉
        }
    
        //初始化最短路径为极大
        for(int i = 1; i <= H; i ++){
            for(int j = 1; j <= W; j ++){
                minDist[0][i][j] = 40000;
                minDist[1][i][j] = 40000;
            }
        }
    
        //寻找两个出口
        int nExit = 0;
        for(int i = 1; i <= W; i ++){
            if(!hasNorthWall(1, i)) {
                exitX[nExit] = 1, exitY[nExit ++] = i;
                map[2 * (1) - 2][2 * (i) - 1] = '-'; //堵上,方便后面统一计算
            }
            if(!hasSouthWall(H, i)) {
                exitX[nExit] = H, exitY[nExit ++] = i;
                map[2 * (H)][2 * (i) - 1] = '-';
            }
        }
        for(int i = 1; i <= H; i ++){
            if(!hasWestWall(i, 1)){
                exitX[nExit] = i, exitY[nExit ++] = 1;
                map[2 * (i) - 1][2 * (1) - 2] = '|';
            }
            if(!hasEastWall(i, W)){
                exitX[nExit] = i, exitY[nExit ++] = W;
                map[2 * (i) - 1][2 * (W)] = '|';
            }
        }
    
        //找最短路径
        dijkstra(0);
        dijkstra(1);
    
        //输出
        int maxMin = 0, mi;
        for(int i = 1; i <= H; i ++){
            for(int j = 1; j <= W; j ++){
                mi = min(minDist[0][i][j], minDist[1][i][j]);
                if(mi > maxMin) maxMin = mi;
            }
        }
        fprintf(fout, "%d\n", maxMin);     
    
        return 0;
    }
    

    因为数据量比较小,最大也就是 38 * 100 个方格,最多每个点 4 条边。while 循环最多执行 38000 次;里面找距离最短的还要 38000 次,但我记得教程讲这个事少,又有缓存的加成,可以认为比较快,也就是38000 ^ 2 次。

    本来以为如果超时了就去搞个堆,或者改 spfa。结果这个 0.084 secs 的速度,还是吓到我了。看 nocow 上说 dijkstra 不算快的样子。我好像觉得,同样的算法,我尽管没有某些加速没有剪枝,写出来似乎总是比别人要快啊……上天眷顾我么??

    Compiling...
    Compile: OK
    
    Executing...
       Test 1: TEST OK [0.000 secs, 4220 KB]
       Test 2: TEST OK [0.000 secs, 4220 KB]
       Test 3: TEST OK [0.000 secs, 4220 KB]
       Test 4: TEST OK [0.000 secs, 4220 KB]
       Test 5: TEST OK [0.000 secs, 4220 KB]
       Test 6: TEST OK [0.084 secs, 4220 KB]
       Test 7: TEST OK [0.028 secs, 4220 KB]
       Test 8: TEST OK [0.084 secs, 4220 KB]
       Test 9: TEST OK [0.084 secs, 4220 KB]
       Test 10: TEST OK [0.084 secs, 4220 KB]
    
    All tests OK.
    YOUR PROGRAM ('maze1') WORKED FIRST TIME!  That's fantastic
    -- and a rare thing.  Please accept these special automated
    congratulations.
    

    使用 spfa

    使用 spfa 算法,队列给了最大长度 3800。同时,改掉判断墙的四个宏定义,写成一个函数,把方向作为一个参数传进去,往每个方向引起的 x、y 的变化也存在数组里了,重复少了一些。

    /*
    ID: ufoshen1
    LANG: C++
    TASK: maze1
    */
    
    #include <cstdio>
    #include <cstdlib>
    
    #define min(x, y) ((x) < (y)? (x): (y))
    
    const int maxW = 38, maxH = 100;
    int W, H;
    char map[2 * maxH + 1][2 * maxW + 1], temp; //地图
    int exitX[2], exitY[2]; //两个出口
    int minDist[2][maxH + 1][maxW + 1]; //对两个出口的最短距离
    int deltaX[4] = {-1, 0, 1, 0}, deltaY[4] = {0, 1, 0, -1};
    
    bool hasWall(int x, int y, int dir){
        switch(dir){ //0N 1E 2S 3W
            case 0: return (map[2 * (x) - 2][2 * (y) - 1] == '-');
            case 1: return (map[2 * (x) - 1][2 * (y)] == '|');
            case 2: return (map[2 * (x)][2 * (y) - 1] == '-');
            case 3: return (map[2 * (x) - 1][2 * (y) - 2] == '|');
        }
    }
    
    void spfa(int exit){
        bool inq[maxH + 1][maxW + 1];//是否在队列中
        minDist[exit][exitX[exit]][exitY[exit]] = 1; //出口距离为1
        int queue[38000][2], start = 0, end = 1; //队列
        queue[0][0] = exitX[exit], queue[0][1] = exitY[exit]; //出口入队
        int x, y, nbrX, nbrY, newD; 
        while(end > start){
            //出队
            x = queue[start][0], y = queue[start ++][1];
            inq[x][y] = false;
            //四个邻居
            for(int dir = 0; dir < 4; dir ++){
                if(hasWall(x, y, dir)) continue;
                //没墙,连通
                nbrX = x + deltaX[dir], nbrY = y + deltaY[dir];
                newD = minDist[exit][x][y] + 1;
                if(minDist[exit][nbrX][nbrY] > newD){
                    minDist[exit][nbrX][nbrY] = newD;
                    if(!inq[nbrX][nbrY]){
                        queue[end][0] = nbrX, queue[end ++][1] = nbrY;
                        inq[nbrX][nbrY] = true;
                    }
                }
            }
        }
    }
    
    int main(){
        FILE *fin = fopen("maze1.in", "r");
        FILE *fout = fopen("maze1.out", "w");
    
        fscanf(fin, "%d %d\n", &W, &H); //下面用%c读取,这里必须\n也过掉
        for(int i = 0; i < 2 * H + 1; i ++){
            for(int j = 0; j < 2 * W + 1; j ++){
                fscanf(fin, "%c", &(map[i][j]));
            }
            fscanf(fin, "%c", &temp);//行末\n要过掉
        }
    
        //初始化最短路径为极大
        for(int i = 1; i <= H; i ++){
            for(int j = 1; j <= W; j ++){
                minDist[0][i][j] = 40000;
                minDist[1][i][j] = 40000;
            }
        }
    
        //寻找两个出口
        int nExit = 0;
        for(int i = 1; i <= W; i ++){
            if(!hasWall(1, i, 0)) {
                exitX[nExit] = 1, exitY[nExit ++] = i;
                map[2 * (1) - 2][2 * (i) - 1] = '-'; //堵上,方便后面统一计算
            }
            if(!hasWall(H, i, 2)) {
                exitX[nExit] = H, exitY[nExit ++] = i;
                map[2 * (H)][2 * (i) - 1] = '-';
            }
        }
        for(int i = 1; i <= H; i ++){
            if(!hasWall(i, 1, 3)){
                exitX[nExit] = i, exitY[nExit ++] = 1;
                map[2 * (i) - 1][2 * (1) - 2] = '|';
            }
            if(!hasWall(i, W, 1)){
                exitX[nExit] = i, exitY[nExit ++] = W;
                map[2 * (i) - 1][2 * (W)] = '|';
            }
        }
    
        //找最短路径
        spfa(0);
        spfa(1);
    
        //输出
        int maxMin = 0, mi;
        for(int i = 1; i <= H; i ++){
            for(int j = 1; j <= W; j ++){
                mi = min(minDist[0][i][j], minDist[1][i][j]);
                if(mi > maxMin) maxMin = mi;
            }
        }
        fprintf(fout, "%d\n", maxMin);     
    
        return 0;
    }
    

    运行速度吓掉了下巴,全部 0.000 秒AC。这两个算法感觉都挺好写的,可能因为刚写过没多久吧……

    Compiling...
    Compile: OK
    
    Executing...
       Test 1: TEST OK [0.000 secs, 4396 KB]
       Test 2: TEST OK [0.000 secs, 4400 KB]
       Test 3: TEST OK [0.000 secs, 4400 KB]
       Test 4: TEST OK [0.000 secs, 4404 KB]
       Test 5: TEST OK [0.000 secs, 4404 KB]
       Test 6: TEST OK [0.000 secs, 4400 KB]
       Test 7: TEST OK [0.000 secs, 4404 KB]
       Test 8: TEST OK [0.000 secs, 4400 KB]
       Test 9: TEST OK [0.000 secs, 4400 KB]
       Test 10: TEST OK [0.000 secs, 4396 KB]
    
    All tests OK.
    Your program ('maze1') produced all correct answers!  This is your
    submission #2 for this problem.  Congratulations!
    

    相关文章

      网友评论

          本文标题:PROB: maze1

          本文链接:https://www.haomeiwen.com/subject/hwyijxtx.html