例63 红与黑
问题描述
有一间长方形的房子,地上铺了红色、黑色两种颜色的正方形瓷砖。你站在其中一块黑色的瓷砖上,只能向相邻的黑色瓷砖移动。请写一个程序,计算你总共能够到达多少块黑色的瓷砖。
输入
包括多个数据集合。每个数据集合的第一行是两个整数W和H,分别表示x方向和y方向瓷砖的数量。W和H都不超过20。在接下来的H行中,每行包括W个字符。每个字符表示一块瓷砖的颜色,规则如下
1)‘.’:黑色的瓷砖;
2)‘#’:白色的瓷砖;
3)‘@’:黑色的瓷砖,并且你站在这块瓷砖上。该字符在每个数据集合中唯一出现一次。
当在一行中读入的是两个零时,表示输入结束。
输出
对每个数据集合,分别输出一行,显示你从初始位置出发能到达的瓷砖数(记数时包括初始位置的瓷砖)。
输入样例
6 9
....#.
.....#
......
......
......
......
......
#@...#
.#..#.
0 0
输出样例
45
(1)编程思路。
设函数f(x, y)计算从点(x,y)出发能够走过的黑瓷砖总数,则
f(x, y) = 1 + f(x - 1, y) + f(x + 1, y) + f(x, y - 1) + f(x, y + 1)
若点(x,y)超过了房子的范围(x < 0 || x >= w || y < 0 || y >= h)或该处为白色瓷砖,则f(x, y)的返回值为0。
编写简单的递归函数即可。在函数中凡是走过的黑色瓷砖不能够被重复走过。可以通过每走过一块瓷砖就将它作标记的方法(最简单的方法是将其标记为白色瓷砖)保证不重复计算任何黑色瓷砖。
(2)源程序。
#include <stdio.h>
int w,h;
char map[21][21];
int f(int x, int y)
{
if(x < 0 || x >= w || y < 0 || y >= h) // 如果走出房子范围
return 0;
if(map[x][y] == '#')
return 0;
map[x][y] = '#'; // 将走过的瓷砖做标记
return 1 + f(x-1, y)+f(x+1,y)+f(x,y-1)+f(x,y+1);
}
int main()
{
int i, j;
while(scanf("%d %d", &h, &w) && w!=0 && h!=0)
{
for (i = 0; i < w; i++)
scanf("%s", map[i]);
for (i = 0; i < w; i++)
for(j = 0; j < h; j++)
if (map[i][j]=='@') printf("%d\n", f(i,j));
}
return 0;
}
习题63
63-1 马走日
问题描述
马在中国象棋以日字形规则移动。
请编写一段程序,给定n*m大小的棋盘,以及马的初始位置(x,y),要求不能重复经过棋盘上的同一个点,计算马可以有多少途径遍历棋盘上的所有点。
输入
第一行为整数T(T < 10),表示测试数据组数。
每一组测试数据包含一行,为四个整数,分别为棋盘的大小以及初始位置坐标n,m,x,y。(0<=x<=n-1,0<=y<=m-1, m < 10, n < 10)
输出
每组测试数据包含一行,为一个整数,表示马能遍历棋盘的途径总数,0为无法遍历一次。
输入样例
1
5 4 0 0
输出样例
32
(1)编程思路。
马走日可以从一个位置跳到其他的8个位置,编写函数void dfs(int x,int y,int step),三个参数的含义是马第step步走到了位置(x,y)处。
在函数中,若马走到的8个位置之一(tx,ty)在棋盘中并且没有走过(vis[tx][ty]为初始值0),则马走到该位置,即递归调用dfs(tx,ty,step+1)。
若step==n*m,表示棋盘所有的位置都走过,计数cnt++。cnt定义为全局变量,初始值设为0。
(2)源程序。
#include <stdio.h>
#include <string.h>
int cnt=0;
int vis[11][11]={0};
int n,m;
int dx[8]={-1,1,-2,2,-2,2,-1,1},dy[8]={-2,-2,-1,-1,1,1,2,2};
void dfs(int x,int y,int step)
{
if (step==n*m)
{
cnt++;
return;
}
int i;
for (i=0;i<8;i++)
{
int tx=x+dx[i];
int ty=y+dy[i];
if (tx<0 || tx>=n || ty<0 || ty>=m || vis[tx][ty]==1)
continue;
vis[tx][ty]=1;
dfs(tx,ty,step+1);
vis[tx][ty]=0;
}
}
int main()
{
int t;
scanf("%d",&t);
while (t--)
{
memset(vis,0,sizeof(vis));
cnt=0;
int x,y;
scanf("%d%d%d%d",&n,&m,&x,&y);
vis[x][y]=1;
dfs(x,y,1);
printf("%d\n",cnt);
}
return 0;
}
63-2 城堡问题
问题描述
图1是一个城堡的地形图,其中符号“#”表示墙,“|”和“-”表示没有墙。
请你编写一个程序,计算城堡一共有多少房间,最大的房间有多大。城堡被分割成m×n(m≤50,n≤50)个方块,每个方块可以有0~4面墙。
输入
输入第一行是两个整数n和m,分别是南北向、东西向的方块数。在接下来的n行里,每行有m个整数,一个整数表示一个方块周围的墙,1表示西墙,2表示北墙,4表示东墙,8表示南墙。每个方块用代表其周围墙的数字之和表示。例如,方块(1,1)的数字11表示该方块周围有1堵西墙,1堵北墙和1堵南墙,即1+2+8=11。城堡的内墙被计算两次,方块(1,1)的南墙同时也是方块(2,1)的北墙。输入的数据保证城堡至少有两个房间。
输出
城堡的房间数、城堡中最大房间所包括的方块数。
输入样例
4 7
11 6 11 6 3 10 6
7 9 6 13 5 15 5
1 10 12 7 13 7 5
13 11 10 8 10 12 13
输出样例
5
9
(1)编程思路。
表示方块周围墙的数字对应二进制数的低4位,每位表示一堵墙。最低位为1(设为第0位),表示西边有墙;第1位为1,表示北边有墙;第2位为1,表示东边有墙;第3位为1,表示南边有墙。可以简单用该数字分别与1、2、4、8进行按位与运算,若运算结果为0,则表示对应位置没有墙。某方向没有墙就可以继续向该方向走,同时方块数加1。
编写简单的递归函数void dfs(int i,int j)求解以位置(i,j)出发的连通块中方块的个数。
(2)源程序。
#include <stdio.h>
int map[51][51],vis[51][51]={0};
int area;
void dfs(int i,int j)
{
if (vis[i][j]) return ;
vis[i][j]=1;
area++;
if ((map[i][j]&1)==0) dfs(i,j-1); // 西边没有墙,向西走
if ((map[i][j]&2)==0) dfs(i-1,j); // 北边没有墙,向北走
if ((map[i][j]&4)==0) dfs(i,j+1); // 东边没有墙,向东走
if ((map[i][j]&8)==0) dfs(i+1,j); // 南边没有墙,向南走
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
int i,j;
for (i=1;i<=n;i++)
for (j=1;j<=m;j++)
scanf("%d",&map[i][j]);
int sum=0,maxArea=0;
for (i=1;i<=n;i++)
for (j=1;j<=m;j++)
{
if(!vis[i][j])
{
sum++;
area=0;
dfs(i,j);
if (maxArea<area) maxArea=area;
}
}
printf("%d\n%d\n",sum,maxArea);
return 0;
}
63-3 棋盘问题
问题描述
在一个给定形状的棋盘(形状可能是不规则的)上面摆放棋子,棋子没有区别。要求摆放时任意的两个棋子不能放在棋盘中的同一行或者同一列,请编程求解对于给定形状和大小的棋盘,摆放k个棋子的所有可行的摆放方案C。
输入
输入含有多组测试数据。
每组数据的第一行是两个正整数,n k(n <= 8 ,k <= n),用一个空格隔开,表示了将在一个n*n的矩阵内描述棋盘,以及摆放棋子的数目。
当为-1 -1时表示输入结束。
随后的n行描述了棋盘的形状:每行有n个字符,其中 # 表示棋盘区域, . 表示空白区域(数据保证不出现多余的空白行或者空白列)。
输出
对于每一组数据,给出一行输出,输出摆放的方案数目C (数据保证C<2^31)。
输入样例
2 1
#.
.#
4 4
...#
..#.
.#..
#...
-1 -1
输出样例
2
1
(1)编程思路。
编写递归函数void DFS(int row,int num)进行逐行搜索,row为当前搜索行,num为已填充的棋子数。
定义数组int vis[10]用于标某一列上是否有棋子,因为每次递归下一行,所以每一行不会有冲突,只需判断这一列上是否有其他棋子。
在第row行放棋子时,在该行找到一列j,如果row行j列没被标记放过棋子(vis[j]==0)且位置在棋盘内(map[i][j]=='#'),那么在该列放上棋子,并标记,即vis[j]=1;之后递归搜索下一行放下一个棋子,调用函数DFS(row+1,num+1)。
若棋子已经用完(num>=k),则记录方案数加1,然后直接返回。
(2)源程序1。
#include<stdio.h>
int vis[10];
char map[10][10];
int ans,k,n; // ans表示方案数,k表示棋子数目,n表示棋盘的大小
void DFS(int row,int num)
{
if(num>=k)
{
ans++;
return;
}
int i,j;
for (i=row;i<n;i++)
{
for (j=0;j<n;j++)
{
if (!vis[j] && map[i][j]=='#')
{
vis[j]=1;
DFS(i+1,num+1);
vis[j]=0;
}
}
}
}
int main()
{
int i;
while (scanf("%d%d",&n,&k))
{
if (n==-1&&k==-1)
break;
for (i=0;i<n;i++)
vis[i]=0;
for (i=0;i<n;i++)
scanf("%s",map[i]);
ans=0;
DFS(0,0);
printf("%d\n",ans);
}
return 0;
}
实际上,每行在放棋子的过程中,要么在该行找一个可行的列放一个棋子,要么在该行不放棋子。按这一思路,可以编写如下的程序。
(3)源程序2。
#include<stdio.h>
int vis[10];
char map[10][10];
int ans,k,n;
void DFS(int row,int num)
{
if(num==k)
{
ans++;
return;
}
if (row>=n) return ;
int j;
for (j=0;j<n;j++) // 当前行放一个棋子
{
if(!vis[j] && map[row][j]=='#')
{
vis[j]=1;
DFS(row+1,num+1);
vis[j]=0;
}
}
DFS(row+1,num); // 当前行不放棋子
}
int main()
{
int i;
while (scanf("%d%d",&n,&k))
{
if (n==-1&&k==-1)
break;
for (i=0;i<n;i++)
vis[i]=0;
for (i=0;i<n;i++)
scanf("%s",map[i]);
ans=0;
DFS(0,0);
printf("%d\n",ans);
}
return 0;
}