《算法竞赛入门经典》习题4-2 正方形 (Squares,ACM,ICPC World Finals 1990,UVa201)——仅提供大体方法
原题及翻译
A children’s board game consists of a square array of dots that contains lines connecting some of the pairs of adjacent dots.
儿童棋盘游戏由一组正方形的点组成,这些点包含连接一些相邻点对的线。
One part of the game requires that the players count the number of squares of certain sizes that are formed by these lines.
游戏的一部分要求玩家计算由这些线组成的特定大小的方块的数量。
For example, in the figure shown below, there are 3 squares— 2 of size 1 and 1 of size 2. (The “size” of a square is the number of lines segments required to form a side.)
例如,在下图中,有3个正方形——2个边长为1的和1个边长为2的。(正方形的“大小”是构成一条边所需的线段数。)
Your problem is to write a program that automates the process of counting all the possible squares.
您的问题是编写一个程序来自动计算所有可能的正方形。
Input
输入
The input file represents a series of game boards.
输入文件表示一系列游戏板。
Each board consists of a description of a square array of n2 dots (where 2<=n<=9) and some interconnecting horizontal and vertical lines.
每个游戏板由一个由n2个点组成的正方形阵列(其中2<=n<=9)和一些相互连接的水平和垂直线组成。
A record for a single board with n2 dots and m interconnecting lines is formatted as follows:
带有n2个点和m条连线的游戏板记录格式如下:
Line 1: n the number of dots in a single row or column of the array
Line 2: m the number of interconnecting lines
第1行: n 阵列每一行或每一列中点的数目
第2行: m m条连线数量
Each of the next m lines are of one of two types:
接下来M条线都有两种类型:
H i j indicates a horizontal line in row i which connects the dot in column j to the one to its right in column j + 1
H i j 表示第i行中连接J+1列中J列的圆点到其右侧的圆点
or或
V i j indicates a vertical line in column i which connects the dot in row j to the one below in row j + 1
V i j 表示第i列中的一条垂直线上,将j行中的点与j+1行中的下一点连接起来。
Information for each line begins in column 1. The end of input is indicated by end-of-file. The first record of the sample input below represents the board of the square above.
每行的信息从第1列开始。输入结束由文件结束指示。下面输入的样本的第一个记录表示上面的方块。
Output
输出
For each record, label the corresponding output with ‘Problem #1’, ‘Problem #2’, and so forth.
对于每个记录,用“问题1”、“问题2”等标记相应的输出。
Output for a record consists of the number of squares of each size on the board, from the smallest to the largest.
记录的输出由板上每个数的平方数组成,从最小到最大。
lf no squares of any size exist, your program should print an appropriate message indicating so.
如果没有任何大小的方块存在,您的程序应该打印一个适当的消息来指示。
Separate output for successive input records by a line of asterisks between two blank lines, like in the sample below.
在两个空白行之间用一行星号分隔连续输入记录的输出,如下面的示例中所示。
Sample Input
样例输入
4
16
H 1 1
H 1 3
H 2 1
H 2 2
H 2 3
H 3 2
H 4 2
H 4 3
V 1 1
V 2 1
V 2 2
V 2 3
V 3 2
V 4 1
V 4 2
V 4 3
2
3
H 1 1
H 2 1
V 2 1
Sample Output
样例输出
Problem #1
2 square (s) of size 1
1 square (s) of size 2
Problem #2
No completed squares can be found.
思路分析
1.先把游戏板抽象出来,看成是一个二维数组,假设游戏板由n的平方个点组成,每个点都是二维数组中相隔一个元素的元素,中间的元素作为连线使用。
2.然后将二维数组元素全部初始化为0,然后将所有的点对应的元素都设置为1,中间连线为0。
3.录入数据,根据数据内容将二维数组中对应的连线元素设置为1。
void Draw_line(char type,int index,int coordinate)
{//根据输入的数据将两点之间的线连起来
switch(type)
{
case 'H':
Game_board[2*index-1][2*coordinate]=1;
break;
case 'V':
Game_board[2*coordinate][2*index-1]=1;
break;
}
}
4.判断边长为n的正方形:
这让我想到了汝佳老师前边提到的蛇形填数的题目,只不过这道题只需要判断一圈就可以。
从某一点出发,先向右走2n(n>1)个单位,判断路径上的数组元素是否都为1,然后向下再走2n(n>1)个单位,同上,以此类推。
如果中间有为0的元素,则回到第一个2n到达的点,如果能够顺利到达出发点,则边长为n的正方形计数器+1,然后再回到第一个2n到达的点,或者从出发点再次出发走2(n+1)个单位。
这种方法是我一开始想到的,判断起来比较麻烦,后来又想了一种比较简单的方法:判断以(i,j)为中心len为半边长的正方形边上的元素是否全为1,即正方形是否完全。
bool Judging_Squares(int n,int i,int j,int len)
{//用于判断以(i,j)为中心len为边长的正方形是否成立
if(i==1||j==1||i==n||j==n) return false;
int flag=1;
for(int a=i-len;a<i+len;a++)
{
if(Game_board[a][j-len]!=1||Game_board[a][j+len]!=1)
flag=0;
}
for(int a=j-len;a<j+len;a++)
{
if(Game_board[i-len][a]!=1||Game_board[i+len][a]!=1)
flag=0;
}
if(flag==1) return true;
else return false;
}
这两部分就是主要的代码了,剩下的就是读入数据和使用函数进行操作,在main函数中进行。
主函数
int main()
{//读入数据并进行相应的操作和判断
int n,number,num=0;
//n用于记录棋盘的大小,number代表要读入的数据行数,num用于判断这是第几次测试
while(scanf("%d\n%d",&n,&number)==2)
{//读入n和number并判断是否读入了两个数据
memset(Game_board,0,sizeof(Game_board));
memset(squares,0,sizeof(squares));
//将游戏板和计数正方形个数的数组初始化为0
num++;
for(int i=1;i<=2*n;i+=2)
{
for(int j=1;j<=2*n;j+=2)
{
Game_board[i][j]=1;
//将游戏板根据n的大小将对应的点在二维数组中标记为1
}
}
struct Types_of_lines tol[number];
//声明一个存储连线类型的结构数组,结构会声明为全局变量
for(int i=0;i<number;i++)
{//读入number行数据
scanf("%c %d %d",&tol[i].type,&tol[i].index,&tol[i].coordinate);
Draw_line(tol[i].type,tol[i].index,tol[i].coordinate);
}
for(int i=1;i<=2*n;i+=2)
{
for(int j=1;j<=2*n;j+=2)
{
for(int len=1;len<=n;len++)
{
if(Judging_Squares(n,i,j,len))
squares[len]++;
}
}
}
int flag=0;
printf("Problem #%d\n\n",num);
for(int len=n;len>0;len--)
{
if(squares[len])
{
printf("%d square (s) of size %d\n",squares[len],len);
flag=1;
}
}
if(flag==0) printf("No completed squares can be found.\n");
printf("\n**********************************\n\n");
}
return 0;
}
在这里我要向看这篇文章的网友道歉,因为上面的代码有错误,也就是因为这个错误,卡了我一下午。
第一个错误:在读入数据的时候必须使用getchar()读取空格,不然读不了全部的数据。
第二个错误是将读入的数据在Game_board描绘出来后用函数判断是否为完全正方形的时候不能用+=2,必须得用++,不然会跳过一些边长为1的正方形。
接下来是正确的答案。
完整代码
#include <stdio.h>
#include <string.h>
void Draw_line(char type,int index,int coordinate);
bool Judging_Squares(int n,int i,int j,int len);
int Game_board[20][20];
int squares[10];
int main()
{
int n,number,num=0,index,coordinate;
char type;
while(scanf("%d\n%d",&n,&number)==2)
{
memset(Game_board,0,sizeof(Game_board));
memset(squares,0,sizeof(squares));
num++;
for(int i=1;i<=2*n;i+=2)
{
for(int j=1;j<=2*n;j+=2)
{
Game_board[i][j]=1;
}
}
for(int i=0;i<number;i++)
{
getchar();
scanf("%c%d%d",&type,&index,&coordinate);
Draw_line(type,index,coordinate);
}
for(int i=1;i<=2*n;i++)
{
for(int j=1;j<=2*n;j++)
{
for(int len=1;len<=n;len++)
{
if(Judging_Squares(n,i,j,len))
squares[len]++;
}
}
}
int flag=0;
printf("Problem #%d\n\n",num);
for(int len=n;len>0;len--)
{
if(squares[len])
{
printf("%d square (s) of size %d\n",squares[len],len);
flag=1;
}
}
if(flag==0) printf("No completed squares can be found.\n");
printf("\n**********************************\n\n");
}
return 0;
}
bool Judging_Squares(int n,int i,int j,int len)
{//用于判断以(i,j)为中心len为边长的正方形是否成立
int flag=1;
for(int a=i-len;a<i+len;a++)
{
if(Game_board[a][j-len]!=1||Game_board[a][j+len]!=1)
flag=0;
}
for(int a=j-len;a<j+len;a++)
{
if(Game_board[i-len][a]!=1||Game_board[i+len][a]!=1)
flag=0;
}
if(flag==1) return true;
else return false;
}
void Draw_line(char type,int index,int coordinate)
{//根据输入的数据将两点之间的线连起来
switch(type)
{
case 'H':
Game_board[2*index-1][2*coordinate]=1;
break;
case 'V':
Game_board[2*coordinate][2*index-1]=1;
break;
}
}
测试
感悟
做ACM的题真的要小心小心再小心,这道题题目给出的输出格式就是一个坑,在处理输入的数据的时候也要小心,重视getchar的使用。