卫星照片(BFS)

前言

        今天早上我刷题的时候,做了一道很烦的题目--卫星地图。就在我去CSDN找答案的时候,我发现大家都是用深搜(DFS)做的。但题目要求用宽搜(BFS)做。那我该怎么办呢?于是我开始了思考......


题目描述        

        农夫约翰(Farmer John)总是想要一个农场的地图,所以他拍摄了一张N行M列的卫星照片。一部分的照片看起来像这样:

..................
..#####.......##..
..#####......##...
..................
#.......###.....#.
#.....#####.......

        他认为每个联通块都是一个谷仓或一头奶牛。联通块由若干个彼此水平或垂直相邻的#符号组成。以下的照片被认为是两个联通块:

....
.#..
..#.
....

        FJ认为一个联通块是谷仓,当且仅当它是一个完整的矩形,否则该联通块是一头奶牛。在第一张照片上面,有三个谷仓(大小分别为2x1,2x5和1x1)和两头奶牛。请计算他的卫星照片中谷仓和奶牛的数量。

输入

第一行是两个用空格分隔的整数N和M(N,M<=800)。
第二到第N+1行每行包含M个字符表示农场地图(且不含空格)。

输出

输出共两行。
第一行是照片中的谷仓数量,第二行是照片中的奶牛数量。

样例组

输入
5 8
#####..#
#####.##
......#.
.###...#
.###..##
输出
2
2

题目难点

1.宽搜

        这道题目由于题目数据范围开到了800*800,导致我们无法使用深搜(递归)的方式来把这到题目做出来。而用宽搜写,虽然比深搜麻烦(而且比深搜更容易错),但是难度也不是特别高。

        我们可以设t,w两个初始变量和数组h(用于储存答案),并根据while(t<w)的方式来循环(宽搜标准格式)。然后,通过这道题目的样例二可以看出这道题目的方向数组应该用四联通的写法。所以这道题的di,dj(走迷宫,铺地板方向数组)可以这么写(0,0可直接省略,节省时间):

int di[]={1,-1,0,0},dj[]={0,0,1,-1};

        或者也可以用二维数组写:

int dij[][]={{1,0},{-1,0},{0,1},{0,-1}};

        紧接着,我们可以套一层循环,将每一种情况逐一判断是否可行(行列都在农场的范围内)。那我们就可以写出以下程序: 

//设农场行数为n,列数为m
for(int k=0;k<4;k++)
{
	int x=h[t][1]+di[k],y=h[t][2]+dj[k];
	if(x>0&&x<=n&&y>0&&y<=m&&a[x][y]==1)
	{
		a[x][y]=0;w++;
		h[w][1]=x;h[w][2]=y;
	}
}

        然后这道题目的难点来了:怎么求连通块是否是矩形?我们可以求出连通块的四个端点,求出如果这个连通块是矩形的面积(具体判断方式见下表) ,再与矩形的面积进行比较就行了。

左上方端点左下方端点右上方端点右下方端点面积
1,54,51,94,954

20

        然后可以发现,有四个数值是重复的,那么就可以只用四个变量储存连通块的四个端点。同时推出公式,公式如下:

//ri,rj,li,lj是连通块的四个端点
//矩形面积=(ri-li+1)*(rj-lj+1)

        那么该怎么得到数据呢?只需要在之前的if语句中加入一行代码即可。

li=min(li,x);ri=max(ri,x);lj=min(lj,y);rj=max(rj,y);

        而最后的比较代码就更简单了。

//ans表示谷仓数量,sum表示奶牛数量。
if(w==(ri-li+1)*(rj-lj+1)) ans++;
else  sum++;

        都已经做到这了,整个的bfs程序想必就很好写出来了。

#define N 1001
int di[]={-1,1,0,0},dj[]={0,0,-1,1},n,m,h[N*N][3],a[N][N],ans=0,sum=0;
void bfs(int i,int j)
{
	int t=0,w=1,x,y,li=i,lj=j,ri=i,rj=j;
	h[1][1]=i;h[1][2]=j;a[i][j]=0;//h数组表示i,j
	while(t<w)
	{
		t++;
		for(int k=0;k<4;k++)
		{
			x=h[t][1]+di[k],y=h[t][2]+dj[k];
			if(x>0&&x<=n&&y>0&&y<=m&&a[x][y]==1)
			{
				li=min(li,x);ri=max(ri,x);lj=min(lj,y);rj=max(rj,y);
				a[x][y]=0;w++;
				h[w][1]=x;h[w][2]=y;
			}
		}
	}
	if(w==(ri-li+1)*(rj-lj+1)) ans++;
	else  sum++;
}

2 读入

        这道题的读入看似要读入char数组,实则不然。想必有人已经注意到了,这里的判断条件是a[i][j]==1。我们可以用int数组(或bool数组)来储存农场的卫星地图。而这样做自然需要一个读入函数。这道题目的读入函数如下:

void cdr(int n,int m)
{//因为是char,所以前面加上c
	char c;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			cin>>c;
			if(c=='.') a[i][j]=0;
			else a[i][j]=1;
		}
	}
}

        至于其他吗,双循环for判断值是否为1(若为1,启用BFS),然后在直接输出ans和sum就可以了。(因此,这道题目主要的难点是宽搜部分)

题目标程

        这道题目的标程如下:

#include<bits/stdc++.h>
#define N 1001
using namespace std;
int di[]={-1,1,0,0},dj[]={0,0,-1,1},n,m,h[N*N][3],a[N][N],ans=0,sum=0;
void bfs(int i,int j)
{
	int t=0,w=1,x,y,li=i,lj=j,ri=i,rj=j;//附初值
	h[1][1]=i;h[1][2]=j;a[i][j]=0;
	while(t<w)
	{
		t++;
		for(int k=0;k<4;k++)
		{
			x=h[t][1]+di[k],y=h[t][2]+dj[k];
			if(x>0&&x<=n&&y>0&&y<=m&&a[x][y]==1)
			{
				li=min(li,x);ri=max(ri,x);lj=min(lj,y);rj=max(rj,y);
				a[x][y]=0;w++;
				h[w][1]=x;h[w][2]=y;
			}
		}
	}
	if(w==(ri-li+1)*(rj-lj+1)) ans++;
	else  sum++;
}
void cdr(int n,int m)
{
	char c;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			cin>>c;
			if(c=='.') a[i][j]=0;
			else a[i][j]=1;
		}
	}
}
int main()
{
	cin>>n>>m;
	cdr(n,m);
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
			if(a[i][j]==1) bfs(i,j);
	}
	cout<<ans<<endl<<sum;
	return 0;
}

         这道题目就这么多。作为一道宽搜铺地板(深搜也是)的基础题目,这道题目的难度还是比较高的,因此做题的时候要仔细一些,不能在一些较小的细节上(如n,m搞反)犯下不该犯的错误。加油!

  • 7
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值