之前RXZ说做了一道题没感觉就没必要写笔记,所以一直没写,但是今天遇到了一道题。非常有意思,是一种我不知道的做法。

洛谷 P1141 01迷宫

由于放链接会发生一些不妙的事情,请自行去洛谷查看。

题意没有任何的弯弯绕绕。

看到题的tag是广搜就先写了个bfs,然后Tle了3个点。

bfs tle代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include <cstdio>
#include <queue>
#include <utility>
#include <cstring>

using namespace std;

const int MAXN = 1e3 + 5;
const int dx[] = {-1, +1, 0, 0};
const int dy[] = {0, 0, +1, -1};

int n, m, ans;
bool visit[MAXN][MAXN], map[MAXN][MAXN];

void input()
{
scanf("%d %d", &n, &m);

for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
scanf("%1d", &map[i][j]);
}

int get(int i, int j)
{
memset(visit, 0, sizeof(visit));
queue<pair<int, int>> q;
int ans = 0;

q.push({i, j});

while (!q.empty())
{
int x = q.front().first, y = q.front().second;
q.pop();

if (!visit[x][y])
{
visit[x][y] = true;
ans++;
}else
continue;

for (int i = 0; i < 4; i++)
{
int nx = x + dx[i], ny = y + dy[i];

if (nx >= 1 && nx <= n && ny >= 1 && ny <= n)
if (map[nx][ny] ^ map[x][y])
q.push({nx, ny});
}
}

return ans;
}

void work()
{
while (m--)
{
int x, y;
scanf("%d %d", &x, &y);

printf("%d\n", get(x, y));
}
}

int main()
{
input();

work();

return 0;
}

非常的不妙,愉快的重写了个dfs自以为能省点时间(然而事实上只减少了0.01s的时间,可以看作误差)。

dfs tle代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include <cstdio>
#include <queue>
#include <utility>
#include <cstring>

using namespace std;

const int MAXN = 1e3 + 5;
const int dx[] = {-1, +1, 0, 0};
const int dy[] = {0, 0, +1, -1};

int n, m, ans;
bool visit[MAXN][MAXN], map[MAXN][MAXN];

void input()
{
scanf("%d %d", &n, &m);

for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
scanf("%1d", &map[i][j]);
}

void dfs(int x, int y)
{
if (!visit[x][y])
{
visit[x][y] = true;
ans++;
}
else
return;

for (int i = 0; i < 4; i++)
{
int nx = x + dx[i], ny = y + dy[i];

if (nx >= 1 && nx <= n && ny >= 1 && ny <= n)
if (map[nx][ny] ^ map[x][y])
dfs(nx, ny);
}
}

void work()
{
while (m--)
{
int x, y;
scanf("%d %d", &x, &y);

memset(visit, 0, sizeof(visit));
ans = 0;
dfs(x, y);
printf("%d\n", ans);
}
}

int main()
{
input();

work();

return 0;
}

再想了10分钟还是没头绪,就无奈的打开了题解(说好的学术模式)。

自豪的说一句,我的代码的可读性比他们的高不少:

dfsAC代码!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include <cstdio>
#include <queue>
#include <cstring>

using namespace std;

const int MAXN = 1e3 + 5;
const int dx[] = {-1, +1, 0, 0};
const int dy[] = {0, 0, +1, -1};

int n, m, used[MAXN][MAXN], ans[MAXN * MAXN + 5];
bool map[MAXN][MAXN];

void input()
{
scanf("%d %d", &n, &m);

for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
scanf("%1d", &map[i][j]);
}

void dfs(int x, int y, int count)
{
if (used[x][y] != -1)
return;

used[x][y] = count;
ans[count]++;

for (int i = 0; i < 4; i++)
{
int nx = x + dx[i], ny = y + dy[i];

if (nx >= 1 && nx <= n && ny >= 1 && ny <= n)
if (map[nx][ny] ^ map[x][y])
dfs(nx, ny, count);
}
}

void work()
{
memset(used, -1, sizeof(used));

for (int i = 1; i <= m; i++)
{
int x, y;
scanf("%d %d", &x, &y);

if (used[x][y] == -1)
dfs(x, y, i);
else
ans[i] = ans[used[x][y]];
}

for (int i = 1; i <= m; i++)
printf("%d\n", ans[i]);
}

int main()
{
input();

work();

return 0;
}

他们判断是否能走的方法真是让我哭笑不得:

1
2
3
4
5
void dfs(int r,int c,int z,int lll){
if (r<0 || r>=n || c<0 || c>=n || f[r][c]!=-1 || s[r][c]-'0'!=z)return;
f[r][c]=lll;ans[lll]++;
dfs(r-1,c,!z,lll);dfs(r+1,c,!z,lll);dfs(r,c-1,!z,lll);dfs(r,c+1,!z,lll);
}

看了我几分钟才看明白是通过z变量来判断是否通,难道他们不知道有个东西叫异或运算符吗?

‘^’ :只有当两个不一样的时候才会返回true,否则返回false,多么方便啊!

总结

是连通块做法,我之前虽然听说过但是不会用,在做完这道题后就初入连通块了!

大致思路就是用ans数组记录连通块的value,used数组(其实叫flag数组更合适)记录每个坐标所属的ans。

在搜索的时候就给连通块标号和计算通路,这样即可AC。

This post was written on August 14, 2022 at 11:16 PM.