洛谷P1141 01迷宫 题解

一条搜索水题,竟然交了10次才a...还是太菜了,怒献一篇题解,思路是记忆化dfs+剪枝。
先看一下题目:(截图拼接的,中间有一条丑陋的线)


image

思路

  1. 先说一说我自己的思路,这道题一眼看上去就是一个深搜,把01用二维数组存下下来,搜索每一个格子然后判断合理情况,记录格子能联通的最大块数,本以为是水题,噼里啪啦一顿dfs交上去,TLE了三个点只有70分,来看看这样做的代码:
    有几个注意点提一下:
  • cnt记录当前搜索的起始格子能联通的最大块数
  • dir数组记录下一步的四个不同走向
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn = 1010;

int ans[100100];
char maze[maxn][maxn];
int n, m, cnt;
int vis[maxn][maxn];
int dir[4][2] = {
    {1, 0}, {0, 1}, {-1, 0}, {0, -1}
};

void dfs(int x, int y, int cur){
    vis[x][y] = 1;
    cnt++;
    for (int i = 0; i < 4; ++i){
        int xx = x + dir[i][0];
        int yy = y + dir[i][1];
        if (xx < 0 || xx >= n || yy < 0 || yy >= n || vis[xx][yy])
            continue;
        int next = maze[xx][yy] - '0';
        if (next == !cur){
            dfs(xx, yy, next);
        }
    }  
}

int main(int argc, char const *argv[]){
    int top = 0;
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; ++i){
        scanf("%s", maze[i]);
    }
    for (int i = 0; i < m; ++i){
        int x, y;
        scanf("%d%d", &x, &y);
        dfs(x-1, y-1, maze[x-1][y-1] - '0');
        ans[top++] = cnt;
        cnt = 0;
        memset(vis, 0, sizeof(vis));
    }
    for (int i = 0; i < top; ++i){
        printf("%d\n", ans[i]);
    }
    return 0;
}
  1. 这样做问题出在哪里呢?我把测试数据看了一下,发现很恶心,n和m分别取到了1000和10000,那显然,memset是很耗时间的,于是我想到把vis数组的标记改为layer,代表所搜索的格子所在的不同联通块。这样,每一次去数新一个格子所能联通的块数就只要更新layer,节省了很大一块时间,成功过掉了第二个点也就是n=1000, m=10000的点,下面是改良后的80分的代码:
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn = 1010;

int ans[100100];
char maze[maxn][maxn];
int n, m, cnt, layer=1;
int vis[maxn][maxn], record[maxn][maxn];
int dir[4][2] = {
    {1, 0}, {0, 1}, {-1, 0}, {0, -1}
};
void dfs(int x, int y, int cur){
    vis[x][y] = layer;
    cnt++;
    for (int i = 0; i < 4; ++i){
        int xx = x + dir[i][0];
        int yy = y + dir[i][1];
        if (xx < 0 || xx >= n || yy < 0 || yy >= n || vis[xx][yy] == layer)
            continue;
        int next = maze[xx][yy] - '0';
        if (next == !cur){
            dfs(xx, yy, next);
        }
    }  
}

int main(int argc, char const *argv[]){
    int top = 0;
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; ++i){
        scanf("%s", maze[i]);
    }
    for (int i = 0; i < m; ++i){
        int x, y;
        scanf("%d%d", &x, &y);
        dfs(x-1, y-1, maze[x-1][y-1] - '0');
        ans[top++] = cnt;
        cnt = 0;
        layer++;
    }

    for (int i = 0; i < top; ++i){
        printf("%d\n", ans[i]);
    }
    return 0;
}
  1. 到了这里,我当时已经想不到其他优化方法了,于是怀疑自己是否用错了方法,也有大佬告诉我这题实际上用广搜随便写,或者并查集加滚动数组,但我不信邪啊...本着锻炼自己dfs+记忆的能力的想法,我苦苦思索,是与否有其他优化方法,果然,有一条很明显的但是一早就被我忽略了:
    同一个联通块所在的格子,能到达的格子数都是相同的。
    这样一看,怎么记忆化就已经很明显了,我们已经用vis数组标记过了当前搜索的格子所在的联通块,用layer表示,那么我们只要再开一个record数组,将已经遍历过的联通块对应的格子数记录下来,那么搜索到相应联通块里的其它格子时,就不用再搜索了,这是一步很大的优化,最终我提交了修改过的代码,100分ac,代码如下:
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn = 1010;
const int maxm = 100100;

int ans[maxm];
char maze[maxn][maxn];
int n, m, cnt, layer=1;
int vis[maxn][maxn], record[maxm];
int dir[4][2] = {
    {1, 0}, {0, 1}, {-1, 0}, {0, -1}
};

void dfs(int x, int y, int cur){
    vis[x][y] = layer;
    record[layer] = ++cnt;
    for (int i = 0; i < 4; ++i){
        int xx = x + dir[i][0];
        int yy = y + dir[i][1];
        if (xx < 0 || xx >= n || yy < 0 || yy >= n || vis[xx][yy] == layer)
            continue;
        int next = maze[xx][yy] - '0';
        if (next == !cur){
            dfs(xx, yy, next);
        }
    }  
}

int main(int argc, char const *argv[]){
    memset(record, -1, sizeof(record));
    int top = 0;
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; ++i){
        scanf("%s", maze[i]);
    }
    for (int i = 0; i < m; ++i){
        int x, y;
        scanf("%d%d", &x, &y);
        if (record[vis[x-1][y-1]] == -1){
            dfs(x-1, y-1, maze[x-1][y-1] - '0');
            ans[top++] = record[vis[x-1][y-1]];
        }else {
            ans[top++] = record[vis[x-1][y-1]];
        }
     
        cnt = 0;
        layer++;
    }
    for (int i = 0; i < top; ++i){
        printf("%d\n", ans[i]);
    }
    return 0;
}

洛谷的界面做的还是很漂亮的。


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