剧情提要:一片m*n个格子的油田,对于每个格子,如果格子里有油(用@表示,没油是*),我们称其为POCKET,如果两个POCKET相邻(包括同行、同列、同对角线的情况),我们称这两个POCKET是同一DEPOSIT。你要做的,就是数出这个油田里有多少个DEPOSIT。
基本思路:这道题其实就是统计该图中有多少个连通分支。我们首先确定使用深度搜索的方式,候选者是当前点的所有相邻点,对于发现的POCKET我们需要做好MARK记录以防重复搜索。注意!当我们发现了一个未发现过的POCKET,那么它就是我们开辟一个新的连通分支的入口,这时候我们计数加1。计算有多少个DEPOSIT,其实就是计算有多少个入口。
难点:每次递归搜索下一个点时,分两种情况,即下一个点是合格的相邻点,或者下一个点就是单纯数组下标的下一个点。
- 下一个点是合格的相邻点:当前点是首次发现的或者已发现且被递归出来的(还有已发现且不是被递归出来的情况,这个时候我们是不要管这个点的)@。是不是首次发现我们可以查mark表,但这个点是递归出来的还是单纯移步出来的我们怎么区别?我们可以在每次找下一个点的时候记录移动方式,也就是说,每次递归都将带着当前结点是怎么来的标记。显然,移动方式只有两种情况,即相邻点扩展和单纯移步。
下一个点是下标+1的点:当前点是*或者已发现且不是被递归出来的(而是单纯移步来的)。注意!这二个条件也巧妙包含了回溯到入口时的情况
我们来看一个粒子:
搜索路径是:1 1->1 2->2 3->3 4->1 4->3 2->1 3->1 4->1 5->2 1->2 2->2 3->2 4->
2 5->3 1->3 2->3 3->3 4->3 5
代码:
#include <iostream>
#include<stdio.h>
#include<queue>
#include<vector>
#define SZ 105
using namespace std;
int m,n;
int cnt;
char ch[SZ][SZ];
int mark[SZ][SZ];
bool flag=false;
int map[][2]={//相邻的八种可能
1,1,
-1,-1,
-1,1,
1,-1,
0,1,
0,-1,
1,0,
-1,0
};
void Try(int x,int y,bool pre){
int nx,ny;
printf("%d %d->",x,y);
if(ch[x][y]=='@'){
if(pre||!pre&&!mark[x][y]){//@来自于递归或者不来自于递归且也还没被扩展过
if(!mark[x][y]){
cnt++;
mark[x][y]=true;
}//不是递归来的,它就是我们开辟某连通集的首元素!因为如果递归来的肯定是已经记录了的
for(int i=0;i<8;i++){//候选者遍历
nx=x+map[i][0];
ny=y+map[i][1];
if((0<nx<=n)&&(0<ny<=m)){//如果邻点没有越界
if(ch[nx][ny]=='@'&&!mark[nx][ny]){//没有到达过且可达
mark[nx][ny]=true;
Try(nx,ny,true);//每次递归代表的是求下一个状态
}
}
}
}
}
if(ch[x][y]=='*'||!pre&&mark[x][y]){//如果本身是‘*’或者是单纯移步得来的已到达的‘@’(即已经被扩展过),那么我们就单纯移步下一个状态
//回到首元素以后我们要继续下一个单纯移步了
pre=false;
if(y==n){
nx=x+1;
ny=1;
if(nx<=m) Try(nx,ny,pre);
}
else{
nx=x;
ny=y+1;
if(ny<=n) Try(nx,ny,pre);
}
}
}
int main() {
while(scanf("%d %d",&m,&n)!=EOF){
getchar();
cnt=0;
if(m==0&&n==0) break;
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
scanf("%c",&ch[i][j]);
mark[i][j]=false;
}
getchar();
}
Try(1,1,false);
printf("%d\n",cnt);
}
return 0;
}
//3 5
//*@*@*
//**@**
//*@*@*
//1 8
//@@****@*
//5 5
//****@
//*@@*@
//*@**@
//@@@*@
//@@**@
//0 0