goj 扫雷(dfs)
Problem Description:
扫雷这个游戏想必各位都是会玩的吧。简单说一下规则,n行m列的格子地图上有分布一些雷。起先每个格子 都是未点开的状态,我们依次点开格子,找出所有雷。有如下3条规则: 1.点开的地方如果相邻的格子(上、下、左、右,左上、左下、右上、右下)有雷,该格子显示一个数字,表 示该格子相邻的地方有几个雷(最多8)。 2.如果点开的地方是雷,则游戏结束。 3.如果点开的地方和相邻的地方都没雷,就自动向外扩张到相邻有雷的地方。 ps:没玩过扫雷不能理解上面说的可以开一下系统自带扫雷试试。不过我觉得我语言表达能力还是可以的, 你们应该能看懂。 给出一副n行m列的地图,'*'号表示雷。'.'表示非雷。 写一个程序找出,最少需要点几下就能找出所有的雷。
Input:
输入包含多组测试,每组测试是一个n行m列的地图(0<n,m<=100),地图只包含'*'或'.'。
Output:
对于每组测试,输出最少需要点几下就能找出所有的雷。
Sample Input:
5 5 ...** **.** ***** ..... *.*.*
Sample Output:
11
解题思路:算是对扫雷的规则有了深刻的认识,典型的dfs。思路:如果点击的某个位置(x,y)是空白并且周围(八个方向)都没有雷,那么就会沿着周围按这个规则递归扩展空白,扩展过程中若遇到某个周围有雷的位置,则不再递归扩展,直接显示出该位置上的数字即可。于是我们先dfs扩展那些能扩展的位置,然后再单独点一下剩下那些有数字的位置即可。显然,这样的点击次数是最少的。
AC代码:
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long LL; 4 const int maxn=105; 5 int n,m,ans,dir[8][2]={{-1,-1},{-1,1},{1,-1},{1,1},{-1,0},{1,0},{0,-1},{0,1}}; 6 char mp[maxn][maxn];bool vis[maxn][maxn]; 7 bool is_border(int x,int y){///判断是否越界 8 if(x>=0&&x<n&&y>=0&&y<m)return true; 9 return false; 10 } 11 bool check(int x,int y){///检查点(x,y)周围是否都不是雷 12 for(int i=0;i<8;++i){ 13 int nx=x+dir[i][0],ny=y+dir[i][1]; 14 if(is_border(nx,ny)&&mp[nx][ny]=='*')return false; 15 } 16 return true; 17 } 18 void dfs(int x,int y){///不必考虑mp[x][y]=='*',因为递归前已都将是雷的排除在外,但是已经访问的要返回,减少递归次数 19 if(vis[x][y])return;///已访问则直接返回 20 vis[x][y]=true; 21 for(int i=0;i<8;++i){ 22 int nx=x+dir[i][0],ny=y+dir[i][1]; 23 if(is_border(nx,ny)&&!vis[nx][ny]&&mp[nx][ny]=='.'){ 24 if(check(nx,ny))dfs(nx,ny);///周围没有雷,则继续dfs扩展能到达的地方 25 else vis[nx][ny]=true;///否则直接标记为T,表示最多只能扩充到当前位置 26 } 27 } 28 } 29 int main(){ 30 while(~scanf("%d%d",&n,&m)){ 31 memset(vis,false,sizeof(vis));ans=0; 32 for(int i=0;i<n;++i)scanf("%s",mp[i]); 33 for(int i=0;i<n;++i) 34 for(int j=0;j<m;++j) 35 if(!vis[i][j]&&mp[i][j]=='.'&&check(i,j))dfs(i,j),ans++;///先递归扩展空白处 36 for(int i=0;i<n;++i) 37 for(int j=0;j<m;++j) 38 if(!vis[i][j]&&mp[i][j]=='.')ans++;///再将未访问点一下即可 39 printf("%d\n",ans); 40 } 41 return 0; 42 }