01迷宫
题目描述
有一个仅由数字 $0$ 与 $1$ 组成的 $n \times n$ 格迷宫。若你位于一格 $0$ 上,那么你可以移动到相邻 $4$ 格中的某一格 $1$ 上,同样若你位于一格 $1$ 上,那么你可以移动到相邻 $4$ 格中的某一格 $0$ 上。
你的任务是:对于给定的迷宫,询问从某一格开始能移动到多少个格子(包含自身)。
输入格式
第一行为两个正整数 $n,m$。
下面 $n$ 行,每行 $n$ 个字符,字符只可能是 $0$ 或者 $1$,字符之间没有空格。
接下来 $m$ 行,每行两个用空格分隔的正整数 $i,j$,对应了迷宫中第 $i$ 行第 $j$ 列的一个格子,询问从这一格开始能移动到多少格。
输出格式
$m$ 行,对于每个询问输出相应答案。
样例 #1
样例输入 #1
样例输出 #1
提示
对于样例,所有格子互相可达。
- 对于 $20%$ 的数据,$n \leq 10$;
- 对于 $40%$ 的数据,$n \leq 50$;
- 对于 $50%$ 的数据,$m \leq 5$;
- 对于 $60%$ 的数据,$n,m \leq 100$;
- 对于 $100%$ 的数据,$1\le n \leq 1000$,$1\le m \leq 100000$。
分析
看到本题的第一反应是用dfs求出每个连通块的点的个数,则连通块中的每个点的可达点个数就是连通块的大小。
样例:
对于样例我们可以看到每个点之间都是相互可达的,故只有一个连通块,大小为4,所以每个点的可达点个数都是4。
实现
普通DFS
一开始我直接只用DFS来求每个连通块的大小,并在搜索完该连通块中的一个点后,把所有点的可达个数都改成改连通块大小,在下次询问时可直接返回结果。最大时间复杂度是$O(n^2)$,最大深度也说$O(n^2)$,数据是$1000$,很不幸出现RE了。
用并查集进行优化
不过加上并查集进行优化后进可以AC了。
这里回顾一下并查集的代码
[并查集模板题]:AcWing 836. 合并集合 - AcWing
并查集模板代码:
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
| #include<iostream> using namespace std; const int N = 1e5+10; int n,m,p[N]; int find(int x) { return x==p[x]?x:p[x]=find(p[x]); } void Union(int a,int b) { int h1=find(a),h2=find(b); if(h1!=h2) { p[h2]=h1; } } int main() { cin>>n>>m; for(int i=1;i<=n;i++) { p[i]=i; } while(m--) { char op[2]; int a,b; cin>>op>>a>>b; if(*op=='M') { Union(a,b); } else { if(find(a)==find(b)) puts("Yes"); else puts("No"); } } }
|
而对于这道题就可以把每个相互可达的点放在一个并查集中,当每次搜索时都把两个相邻的并查集连接同时更新并查集的大小。
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
| #include<iostream> #include<algorithm> #include<cstring> using namespace std; const int N =1e6+100; const int M =1010; int f[N],p[N],n,m; char g[M][M]; int find(int x) { return x==p[x]?x:p[x]=find(p[x]); }
void Union(int a,int b) { int h1=find(a),h2=find(b); if(h1!=h2) { p[h2]=h1; f[h1]+=f[h2]; } }
int dfs(int x,int y) { if(p[x*n+y]!=-1) return find(x*n+y); p[x*n+y]=x*n+y; f[x*n+y]=1; int dx[]={1,-1,0,0},dy[]={0,0,-1,1}; for(int i=0;i<4;i++) { int nx=x+dx[i],ny=y+dy[i]; if(nx>=0&&nx<n&&ny>=0&&ny<n&&g[x][y]!=g[nx][ny]) { int tmp=dfs(nx,ny); Union(x*n+y,tmp); } } return find(x*n+y); } int main() { cin>>n>>m; memset(p,-1,sizeof p); for(int i=0;i<n;i++) { cin>>g[i]; } while(m--) { int x,y; cin>>x>>y; cout<<f[dfs(x-1,y-1)]<<endl; } return 0; }
|
不过这种方法同样会有栈溢出的风险,毕竟他只是加速了我们搜索每层的时间,但是并不能让我们的搜索层数变少。而洛谷的数据可以让并查集的方法过,而不能让dfs过,但是我在本地运行时,当数据量非常大时,dfs+并查集的方法一样会使栈溢出,所以更优的选择应该是循环+并查集或者bfs,后续会补上这两种方法。