关于用搜索方法求解联通块问题

点击打开链接

题目描述

有一个仅由数字0与1组成的n×n格迷宫。若你位于一格0上,那么你可以移动到相邻4格中的某一格1上,同样若你位于一格1上,那么你可以移动到相邻4格中的某一格0上。

你的任务是:对于给定的迷宫,询问从某一格开始能移动到多少个格子(包含自身)。

输入输出格式

输入格式:

输入的第1行为两个正整数n,m。

下面n行,每行n个字符,字符只可能是0或者1,字符之间没有空格。

接下来m行,每行2个用空格分隔的正整数i,j,对应了迷宫中第i行第j列的一个格子,询问从这一格开始能移动到多少格。

输出格式:

输出包括m行,对于每个询问输出相应答案

输入样例#1:   复制
2 2
01
10
1 1
2 2
输出样例#1:   复制
4
4

首先可以想到用普通的BFS来求解,但是这时候有30%的数据会TLE。

由题意我们可以得出一个结论,同一联通块中所有的格子所求的总数都是一样的。所以就可以得到一个剪枝,先求出联通块的数量,同一联通块的答案只需求一次即可。

所以问题便转化成为了求联通块的数量,在下面提供两个方法:

方法一:

用DFS求解。使用一个 tot作为计数,将已经遍历过的联通块标记出来。

void dfs(int x,int y){
    tot++; //记录答案
    tot[now][0]=x,ans[now][1]=y; //记录连通块的格子
    for(int i=0;i<4;i++){ //搜索下一格
        if(check(nx,ny)&&!v[nx][ny]&&a[x][y]!=a[nx][ny]){
            //越界、访问、颜色判断
            v[nx][ny]=true; //记录访问状态
            dfs(nx,ny);     //下一格
        }
    }
}
再在主函数中提前遍历全图,其中需要记录下同一联通块,方法见下:

for(int i=1;i<=n;i++)
    for(int j=1;j<=n;j++) if(!v[i][j]){ //枚举格子,没有访问过就DFS
        v[i][j]=true;                   //这格被访问
        now=0;                          //总数记为0
        dfs(i,j);                       //从这格开始搜索
        for(int i=1;i<=now;i++) f[ans[i][0]][ans[i][1]]=now; //依次赋值,重复使用并没有问题
    }
方法二:

BFS+并查集求解。首先将初始块作为祖先节点,将同一联通块所有其他的位置都连接到此点上,更新同一联通块所有的点的答案为祖先的值。

这里还要用到一个哈希算法的技巧:将二维数组一维化

int calc(int x,int y)
{
	return (x-1)*n+y;
}
 相当于将二维数组依次向后排序- -

在查询本节点的答案时,需要查询祖先节点的答案,但是祖先节点是一维的形式在并查集里存在,因此需要将它复原。

inline node ID(int k)
{
	int x = k/n;
	int y = k%n;
	if(y==0) y = n;
	else x++;
	return node(x,y);
}

底下附上完整代码:

#include<bits/stdc++.h>
using namespace std;

const int maxn = 1005;
int n,m,ans,x,y,res;
int dir[4][2] = {1,0,-1,0,0,1,0,-1};
int d[maxn][maxn];
int g[maxn][maxn];
string s;
int pre[1010050];
bool vis[maxn][maxn];

struct node{
	int x;int y;
	node(int x_,int y_)
	{
		x = x_;
		y = y_;
	}
};

inline node ID(int k)
{
	int x = k/n;
	int y = k%n;
	if(y==0) y = n;
	else x++;
	return node(x,y);
}

int find(int x)
{
	if(pre[x]==x) return x;
	else return pre[x] = find(pre[x]);
}

inline int calc(int x,int y)
{
	return (x-1)*n+y;
}

int bfs(int x,int y)
{
	if(d[x][y]) return d[x][y];
	if(pre[calc(x,y)]!=calc(x,y)){
		node temp = ID(find(calc(x,y)));
		d[x][y] = d[temp.x][temp.y];
		return d[temp.x][temp.y];
	}
	int res = 1;
	queue<node> q;
	q.push(node(x,y));
	while(!q.empty())
	{
		node now = q.front();
		q.pop();
		vis[now.x][now.y] = 1;
		for(int i=0;i<4;i++)
		{
			int newx = now.x+dir[i][0];
			int newy = now.y+dir[i][1];
			if(g[newx][newy]!=g[now.x][now.y]&&newx>=1&&newx<=n&&newy>=1&&newy<=n&&!vis[newx][newy])
			{
				res++;
				pre[calc(newx,newy)] = calc(now.x,now.y);
				q.push(node(newx,newy));
				vis[newx][newy] = 1; 
			}
		}
	}
	d[x][y] = res;
	return res;
}

int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		cin>>s;
		int len = s.length();
		for(int j=0;j<len;j++)
		{
			if(s[j]=='0') g[i][j+1] = 0;
			else g[i][j+1] = 1;
			pre[(i-1)*n+j+1] = (i-1)*n+j+1;
		}
	}
	for(int j=0;j<m;j++)
	{
		cin>>x>>y;
		cout<<bfs(x,y)<<endl;
	}
	return 0;
}



 



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

总想玩世不恭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值