http://acm.hdu.edu.cn/showproblem.php?pid=1312
Red and Black
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 10078 Accepted Submission(s): 6282
Problem Description
There is a rectangular room, covered with square tiles. Each tile is colored either red or black. A man is standing on a black tile. From a tile, he can move to one of four adjacent tiles. But he can't move on red tiles, he can move only on black tiles.
Write a program to count the number of black tiles which he can reach by repeating the moves described above.
Write a program to count the number of black tiles which he can reach by repeating the moves described above.
Input
The input consists of multiple data sets. A data set starts with a line containing two positive integers W and H; W and H are the numbers of tiles in the x- and y- directions, respectively. W and H are not more than 20.
There are H more lines in the data set, each of which includes W characters. Each character represents the color of a tile as follows.
'.' - a black tile
'#' - a red tile
'@' - a man on a black tile(appears exactly once in a data set)
There are H more lines in the data set, each of which includes W characters. Each character represents the color of a tile as follows.
'.' - a black tile
'#' - a red tile
'@' - a man on a black tile(appears exactly once in a data set)
Output
For each data set, your program should output a line which contains the number of tiles he can reach from the initial tile (including itself).
Sample Input
6 9 ....#. .....# ...... ...... ...... ...... ...... #@...# .#..#. 11 9 .#......... .#.#######. .#.#.....#. .#.#.###.#. .#.#..@#.#. .#.#####.#. .#.......#. .#########. ........... 11 6 ..#..#..#.. ..#..#..#.. ..#..#..### ..#..#..#@. ..#..#..#.. ..#..#..#.. 7 7 ..#.#.. ..#.#.. ###.### ...@... ###.### ..#.#.. ..#.#.. 0 0
Sample Output
45 59 6 13
第一次写是用的是并查集,因为还不会DFS。用并查集时想不到怎样用二维数组,所以只能用一维数组,用行和列的关系表示全部数据,为了使用并查集转换成一维数组。而且写完以后,运行第二组数据不对,因为没有把全部可连的路连起来,所以又加了一次合并,才正确,个人感觉,写这种题用并查集不稳定,或者说不保险,因为如果遇到更特殊数据那就不是加一次合并的问题了(没准要进行N次,虽然N是有限整数,但一定会超时)。
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define MAX 10000
struct SET
{
int S;//按照顺序是第几个数,从0开始
char J;//存放'.','@','#'
}Set[MAX+10];
int Find(int x)
{
int r=x,i=x,j;
while(Set[r].S!=r)
r=Set[r].S;
while(i!=r)
{//路径压缩 ,很关键
j=Set[i].S;
Set[i].S=r;
i=j;
}
return r;
}
void Merge(int x,int y)
{//合并
x=Find(x);
y=Find(y);
if(y<x)
Set[x].S=y;
if(x<y)
Set[y].S=x;
}
int main()
{
int i,j,k,T,M,N,sum,num,I,J;
while(scanf("%d %d",&N,&M)&&(N!=0||M!=0))
{
getchar();
for(i=0;i<M;i++)//给Set[].S赋值0~M*N-1
{
for(j=0;j<N;j++)
{
num=N*i+j;
scanf("%c",&Set[num].J);
if(Set[num].J=='@')
{
I=i;
J=j;
}
Set[num].S=num;
}
getchar();
}
for(i=0;i<M;i++)
{
for(j=0;j<N;j++)
{//每一次都执行下边四个if,保证指向最小的数(通过并查集把联通的点指向最小的那个数)
num=N*i+j;
//下面分别搜索上下左右,如果是'.'或'@'的话就合并
if((Set[num].J=='.'||Set[num].J=='@')&&i-1>=0&&(Set[N*(i-1)+j].J=='.'||Set[N*(i-1)+j].J=='@'))//上
Merge(num,N*(i-1)+j);
if((Set[num].J=='.'||Set[num].J=='@')&&i+1<M&&(Set[N*(i+1)+j].J=='.'||Set[N*(i+1)+j].J=='@'))//下
Merge(num,N*(i+1)+j);
if((Set[num].J=='.'||Set[num].J=='@')&&j-1>=0&&(Set[N*i+j-1].J=='.'||Set[N*i+j-1].J=='@'))//左
Merge(num,N*i+j-1);
if((Set[num].J=='.'||Set[num].J=='@')&&j+1<N&&(Set[N*i+j+1].J=='.'||Set[N*i+j+1].J=='@'))//右
Merge(num,N*i+j+1);
}
}
for(i=0;i<M;i++)
{
for(j=0;j<N;j++)
{//每一次都执行下边四个if,保证指向最小的数(通过并查集把联通的点指向最小的那个数)
num=N*i+j;
//下面分别搜索上下左右,如果是'.'或'@'的话就合并
if((Set[num].J=='.'||Set[num].J=='@')&&i-1>=0&&(Set[N*(i-1)+j].J=='.'||Set[N*(i-1)+j].J=='@'))//上
Merge(num,N*(i-1)+j);
if((Set[num].J=='.'||Set[num].J=='@')&&i+1<M&&(Set[N*(i+1)+j].J=='.'||Set[N*(i+1)+j].J=='@'))//下
Merge(num,N*(i+1)+j);
if((Set[num].J=='.'||Set[num].J=='@')&&j-1>=0&&(Set[N*i+j-1].J=='.'||Set[N*i+j-1].J=='@'))//左
Merge(num,N*i+j-1);
if((Set[num].J=='.'||Set[num].J=='@')&&j+1<N&&(Set[N*i+j+1].J=='.'||Set[N*i+j+1].J=='@'))//右
Merge(num,N*i+j+1);
}
}
//要进行两次合并,因为一次不一定能把应该在一起的合并到一起
for(i=0,sum=0;i<M*N;i++)//查看有几个与I,J对应的头相同
if(Set[i].S==Set[N*I+J].S)
sum+=1;
printf("%d\n",sum);
}
return 0;
}
第二次用DFS写的,显然简洁明了
#include<stdio.h>
#include<string.h>
char map[30][30];//存放'.','@','#'
bool judge[30][30];//标记是否走过
int sum;//可走路的大小
int x,y;
int M,N;//M行,N列
void DFS(int X,int Y)
{
int i,j;//这里的变量不能为全局变量
judge[X][Y]=true;//标记已走
for(i=-1;i<=1;i++)
{
for(j=-1;j<=1;j++)
{//分别对左,下,上,右进行DFS
if(!(i==-1&&j==0||i==0&&j==-1||i==0&&j==1||i==1&&j==0))//排除除上下左右以外的其他所有组合
continue;
x=X+j;
y=Y+i;
if(x>=0&&x<=N-1&&y>=0&&y<=M-1&&!judge[x][y]&&map[x][y]!='#')
{//不能越界,不能走'#',不走已标记的
sum+=1;
DFS(x,y);
}
}
}
}
int main()
{
int i,j,I,J;//I,J为起点位置
while(scanf("%d %d",&M,&N)&&!(M==0&&N==0))
{
memset(judge,false,sizeof(judge));
getchar();
for(i=0;i<N;i++)
{
for(j=0;j<M;j++)
{
scanf("%c",&map[i][j]);
if(map[i][j]=='@')
{
I=i;
J=j;
}
}
getchar();
}
sum=1;
DFS(I,J);
printf("%d\n",sum);
}
return 0;
}