PROB: maze1

题目来自 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!
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,880评论 25 707
  • 旧叶阅读 139评论 0 0
  • 心若计较,处处都有怨言;心若放宽,时时都是春天。世间不如意事十之八九,能对你百依百顺的人,能让你如愿以偿的事都很少...
    f167ccfa15ed阅读 144评论 0 0
  • 儿子上幼儿园的时候,有一天他奶奶接她放学回来,带回两只小鸡崽。说是幼儿园大门口一个女人兜售,十元两只。小鸡可爱,小...
    1K1阅读 287评论 0 0
  • 今天,“感恩”一词虽然常常被人们提起。然而,在喧嚣的社会里,孕育了太多烦躁的人心,又有多少个人能真正的做到“感恩”...
    公子九夜阅读 163评论 0 0