PROB: maze1

题目来自 USACO
题目翻译见 NOCOW

思路

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

AC代码: 用 dijkstra

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

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

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

/*
ID:
LANG: C++
TASK: maze1
*/

#include 
#include 

#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 
#include 

#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)