油田数矿

剧情提要:一片m*n个格子的油田,对于每个格子,如果格子里有油(用@表示,没油是*),我们称其为POCKET,如果两个POCKET相邻(包括同行、同列、同对角线的情况),我们称这两个POCKET是同一DEPOSIT。你要做的,就是数出这个油田里有多少个DEPOSIT。

基本思路:这道题其实就是统计该图中有多少个连通分支。我们首先确定使用深度搜索的方式,候选者是当前点的所有相邻点,对于发现的POCKET我们需要做好MARK记录以防重复搜索。注意!当我们发现了一个未发现过的POCKET,那么它就是我们开辟一个新的连通分支的入口,这时候我们计数加1。计算有多少个DEPOSIT,其实就是计算有多少个入口。

难点:每次递归搜索下一个点时,分两种情况,即下一个点是合格的相邻点,或者下一个点就是单纯数组下标的下一个点。

  1. 下一个点是合格的相邻点:当前点是首次发现的或者已发现且被递归出来的(还有已发现且不是被递归出来的情况,这个时候我们是不要管这个点的)@。是不是首次发现我们可以查mark表,但这个点是递归出来的还是单纯移步出来的我们怎么区别?我们可以在每次找下一个点的时候记录移动方式,也就是说,每次递归都将带着当前结点是怎么来的标记。显然,移动方式只有两种情况,即相邻点扩展单纯移步
  2. 下一个点是下标+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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值