传送门:https://www.luogu.org/problemnew/show/P1141#sub
题目大意:
有一个仅由数字000与111组成的n×n格迷宫。若你位于一格0上,那么你可以移动到相邻4格中的某一格1上,同样若你位于一格1上,那么你可以移动到相邻4格中的某一格0上。
你的任务是:对于给定的迷宫,询问从某一格开始能移动到多少个格子(包含自身)
输入样例#1:
2 2
01
10
1 1
2 2
输出样例#1:
4
4
分割线:
这题因为给的数据,n≤1000,m≤100000 较大,所以正常情况下BFS 和 DFS 都会超时,这里记录了3种方法,另外网上还有一种查并集的方法。
本题是其实是求以01可以相互到达的连通分量,互相到达,就是(i,j)如果可以到(k,l),那么(k,l)也必点可以到(i,j),所以同一连通分量中各个点所能到达的格子数目相等。
先介绍BFS算法吧:
本题也是推荐用BFS来写,比较朴实。只需把每一整块连通块看成一个整体,再用一个a数组去记录这个
连通块的值就可以了。
#include<bits/stdc++.h>
using namespace std;
char mp[1010][1010];//地图
int flag[1010][1010];//判断这个点是否被查找过。
int n,m,sum;
struct node{
int x,y;
};
queue<node> q;
int a[1000001];//记录连通块的值
int dr[4][2] = {{1,0},{-1,0},{0,1},{0,-1}};//4个方向
void bfs(){
int d=0;
for(int i = 1;i<=n;i++){ //遍历整张图的点
for(int j = 1;j <= n;j++){
if(flag[i][j]==0){ //此点在之前没有被用到过,单独的联通分量
d++;//记录多少个连通块
node v;v.x=i;v.y=j;
q.push(v);
flag[i][j] = d;
sum = 1;//此连通块的总个数
while(!q.empty()){
node u = q.front();
q.pop();
int nx,ny;
for(int k = 0;k<4;k++){
nx = u.x + dr[k][0];
ny = u.y + dr[k][1];
if(flag[nx][ny]==0 && nx>=1 && ny>=1 && nx<=n && ny<=n && mp[nx][ny]!=mp[u.x][u.y])
{
sum ++;
flag[nx][ny] = d; //x,y级x,y所能到的点都属于本连通块
node v2;v2.x = nx;v2.y = ny;
q.push(v2);
}
}
a[d]=sum;//第d块连通的总数为sum;
}
}
}
}
}
int main(){
cin>>n>>m;
for(int i = 1;i <= n;i++)
for(int j = 1;j <= n;j++)
cin>>mp[i][j]; //读入
bfs();
for(int i=0;i<m;i++){
int sx,sy;//读入每次要判断的值直接访问
cin>>sx>>sy;
cout<<a[flag[sx][sy]]<<endl;//只要知道此点属于第几连通块即可
}
return 0;
}
分割线:
接下来是DFS算法,这种在本题比较容易超时,也是优化了几次后才可以,dfs第一次优化是记录的以前查找过的点,并且类似BFS中将属于同一连通块的点染成相同的,但是对于m,n较大的时候,在每次DFS后刷同一连通块值的时候会循环次数较多超时,即:
for(int i = 0;i < m; ++i){
int x,y;
ans=0;
cin>>x>>y;
if(bmp[x][y]>0)
cout<<bmp[x][y]<<endl;
else{
dfs(x,y);
cout<<ans<<endl;
for(int j = 1; j <= n;j++)
for(int k = 1; k <= n;k++)
if(bmp[j][k]==0) bmp[j][k]=ans;//这里多了一个 m*n*n
}
}
所以这里我们把第一次搜索到的连通块的点作为跟,而其他所有在此连通块的点都指向这个根。
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1001;
int mp[maxn][maxn];
int bmp[maxn][maxn];
int genx[maxn][maxn]; //找到此点的根x 坐标
int geny[maxn][maxn];//找到此点的根y坐标
int n,m,ans,curx,cury;
int fx[4][2]={{1,0},{-1,0},{0,1},{0,-1}};
void dfs(int x,int y){
if(x>n || y>n||x<1||y<1) return;
if(bmp[x][y]!=-1) return;
ans++;
genx[x][y] = curx; //把根符值到x,y点
geny[x][y] = cury;
bmp[x][y]=0;
for(int i=0;i<4;i++){
int nx,ny;
nx=x+fx[i][0];
ny=y+fx[i][1];
if(mp[nx][ny]!=mp[x][y])
dfs(nx,ny);
}
}
int main(){
cin>>n>>m;
memset(bmp,-1,sizeof(bmp));//这里bmp用-1当作未被遍历过,0用来表示被遍历到,最后用来记录根点所能到达的块数总和
for(int i = 1;i <= n; ++i)
{
string s;cin>>s;
for(int j = 0;j<n;j++){
mp[i][j+1] = s[j]-'0';
} //各种方法读入。。
}
for(int i = 0;i < m; ++i){
int x,y;
ans=0;
cin>>x>>y;
if(bmp[genx[x][y]][geny[x][y]]>0) //值判断根节点是否被遍历过就可
cout<<bmp[genx[x][y]][geny[x][y]]<<endl;
else{
curx = x;cury=y; //新的连通块,新的根
dfs(x,y);
cout<<ans<<endl;
bmp[x][y] = ans;//这里要把根自己也给符值给自己,避免后面重复提问根
genx[x][y] = x;
geny[x][y] = y;
}
}
return 0;
}
分割线:
最后这种方法也是DFS,只是思路很新奇,并且时间复杂度较低,特此记录。
首先,求连通块应该都知道了,我们用单独的一个数组a[10000001][2]来记录属于当前连通块的所有点的坐标,再统一染值。
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1010;
int mp[maxn][maxn];//地图
int f[maxn][maxn]; //值
bool v[maxn][maxn];//是否走过
int ans[1000001][2];//记录连通块中点坐标 ,因为m较大,这里的ans需要开大点
int dr[4][2]={{1,0},{-1,0},{0,1},{0,-1}};
int now,n,m;//now用来记录当前连通块中的值
void dfs(int x,int y){
now++;//找到这个连通块中所有点
ans[now][0]=x;ans[now][1]=y;//记录好这个连通块的各个点坐标,即使和下次地址重复也没事,now会更新
for(int i = 0;i < 4;i++){
int nx = x + dr[i][0];
int ny = y + dr[i][1];
if(nx>=1 && ny>=1 && nx<=n && ny<=n && !v[nx][ny]&&mp[nx][ny]!=mp[x][y]){
v[nx][ny] = 1;//记录走过
dfs(nx,ny);
}
}
}
int main(){
cin>>n>>m;
//memset(v,0,sizeof(v));
for(int i = 1;i <= n; ++i)
{
string s;cin>>s;
for(int j = 0;j<n;j++){
mp[i][j+1] = s[j]-'0';
}
}
for(int i = 1;i <= n; ++i) //遍历整张图
for(int j = 1;j <= n; ++j){
if(!v[i][j]){
v[i][j] = 1;
now = 0;//每次新的连通块都将now清0
dfs(i,j);
for(int k = 1;k<= now;k++){ //走完一次连通块后 统一染色
f[ans[k][0]][ans[k][1]] = now;
}
}
}
for(int i = 0;i < m;++i){
int x,y;cin>>x>>y;
cout<<f[x][y]<<endl; //直接输出记录后的点的值即可
}
return 0;
}
最后:
progranmming is the most fun you can with your clothes on.