【问题描述】
给定一个 N × M 的方格矩阵,矩阵中每个方格标记 0 或者 1 代表这个方格
是不是有人踩过。
已知一个人可能从任意方格开始,之后每一步只能向右或者向下走一格。
走了若干步之后,这个人可以离开矩阵。这个人经过的方格都会被标记为 1,
包括开始和结束的方格。注意开始和结束的方格不需要一定在矩阵边缘。
请你计算至少有多少人在矩阵上走过。
【输入格式】
输入第一行包含两个整数 N、M。
以下 N 行每行包含 M 个整数 (0/1),代表方格矩阵。
【输出格式】
输出一个整数代表答案。
【样例输入】
5 5
00100
11111
00100
11111
00100
【样例输出】
3
试题 G: 估计人数 10
【数据规模与约定】
对于所有评测用例,1 ≤ N, M ≤ 20,标记为 1 的方格不超过 200 个。
(gif图中,红色为广度优先搜索的路径,绿色为最终确立的最长有效路径,蓝色是可行走的路块)
本题使用C语言解题,读者可通过解除下列代码中第三行的注释符号并编译运行以获得该程序的图形化演示。
注意,graphics.h是C++语言的第三方库(easyx库),如要图形化演示,请配置相应头文件。
本题的解决思路是建立一个队列后,遍历map,并用最左上方的map值为1的点作为开始点,然后用广度优先搜索算法来遍历行走出来的最长路径,并记录之中的有效点的数量(map的元素中点值为’1’的点)。
其中拥有最多有效点的一条已被探明的完整路径将代表一个人走过这样的一条路径,同时,map的元素中在此路径上的点值均变成’2’,以表示该路仍可行走,但已“失效”。
在找到一个人后,再次寻找一个最左上方的map值为1的点作为开始点,重复上面步骤。
直至,无法再找一个最左上方的map值为1的点作为开始点,结束遍历。
此时,找到的所有人的数量就是至少有多少人在矩阵上走过的答案。
#include <stdio.h>
#include <stdlib.h>
//#define DEBUG
#ifdef DEBUG
#include <graphics.h>
#define BLOCK_WIDTH (10)
#define WAIT_TIME (500)
#endif // DEBUG
int N, M;
int people;
char **map;
struct BLOCK {
int x;
int y;
int goodSet;//经过的有效点个数
BLOCK *lastp;//队列的上一个点
BLOCK *nextp;//队列的下一个点
BLOCK *lastPointp;//当前路径的上个点
}*mostheadp,*headp, *endp;//队列的起始点,当前队列头结点,当前队列尾结点
void createQueue() {//创建队列
mostheadp = (BLOCK*)malloc(sizeof(BLOCK));
endp = headp = mostheadp;
endp->x = endp->y = endp->goodSet = 0;
endp->lastp = endp->nextp = endp->lastPointp = NULL;
}
void deleteQueue() {//删除队列:回收队列内存
mostheadp = endp;
while (mostheadp = mostheadp->lastp) {
free(endp);
endp = mostheadp;
}
free(endp);
}
void push(int x, int y, BLOCK *lastPointp) {//入列
BLOCK *nowp = (BLOCK*)malloc(sizeof(BLOCK));
nowp->x = x;
nowp->y = y;
nowp->lastp = endp;
nowp->nextp = NULL;
nowp->lastPointp = lastPointp;
endp->nextp = nowp;
endp = nowp;
if (lastPointp) {//作为继承点
nowp->goodSet = lastPointp->goodSet;
if (map[y][x] == '1')//要走的格子没有被走过
++nowp->goodSet;
}
else {//作为开始点
nowp->goodSet = 1;
}
}
void pop(BLOCK **nowp) {//出列
headp = headp->nextp;
*nowp = headp;
}
bool check(int x, int y) {//检查该点是否可走
if (x >= 0 && x < M&&y >= 0 && y < N) {
if (map[y][x] > '0')
return true;
}
return false;
}
#ifdef DEBUG
void print() {//输出map的图形化表示
int i, j;
for (i = 0; i < N; ++i)
for (j = 0; j < M; ++j) {
switch (map[i][j]) {
case '0':setfillcolor(WHITE); break;
case '1':setfillcolor(LIGHTBLUE); break;
case '2':setfillcolor(LIGHTGREEN); break;
}
fillrectangle(j*BLOCK_WIDTH, i*BLOCK_WIDTH, (j + 1)*BLOCK_WIDTH, (i + 1)*BLOCK_WIDTH);
}
Sleep(WAIT_TIME);
}
#endif // DEBUG
void start() {
createQueue();//创建队列
int i, j;
for (i = 0; i < N; ++i) {
for (j = 0; j < M; ++j) {//遍历所有点,以寻找开始点
if (map[i][j] > '0') {//发现可以走的路
if (map[i][j] == '1') {//如果这格路未走过,则可以作为开始点
push(j, i, NULL);//放置开始点
struct BLOCK *nowp;
struct BLOCK *goodSetp = NULL;//存储一条经过的有效点(值为1的块)最多的路径
while (headp->nextp) {//队列里有东西就一直继续,直至队列被清空,即可行路径搜索完毕
pop(&nowp);//开始广度优先搜索
#ifdef DEBUG
setfillcolor(LIGHTRED);
fillrectangle(nowp->x*BLOCK_WIDTH, nowp->y*BLOCK_WIDTH, (nowp->x + 1)*BLOCK_WIDTH, (nowp->y + 1)*BLOCK_WIDTH);
Sleep(WAIT_TIME);
#endif // DEBUG
if (goodSetp) {
if (goodSetp->goodSet < nowp->goodSet)
goodSetp = nowp;
}
else
goodSetp = nowp;
//向右走
if (check(nowp->x + 1, nowp->y)) {//右面一格可以走
push(nowp->x + 1, nowp->y, nowp);
}
//向下走
if (check(nowp->x, nowp->y + 1)) {//下面一格可以走
push(nowp->x, nowp->y + 1, nowp);
}
}
++people;
while (goodSetp) {
map[goodSetp->y][goodSetp->x] = '2';
goodSetp = goodSetp->lastPointp;
#ifdef DEBUG
print();
#endif // DEBUG
}
}
}
}
}
printf("至少%d人", people);
deleteQueue();//删除队列
}
int main() {
int i;
//数据载入
scanf_s("%d%d", &N, &M);
map = (char**)malloc(sizeof(char*)*N);
for (i = 0; i < N; ++i) {
map[i] = (char*)malloc(sizeof(char)*(M + 1));
scanf_s("%s", map[i], M + 1);
}
#ifdef DEBUG
initgraph(BLOCK_WIDTH * N, BLOCK_WIDTH * M);
setbkcolor(WHITE);
cleardevice();
setlinecolor(BLACK);
print();
#endif // DEBUG
start();
#ifdef DEBUG
Sleep(3000);
closegraph();
#endif // DEBUG
//回收内存
for (i = 0; i < N; ++i)
free(map[i]);
free(map);
return 0;
}
纯C运行结果
map的图形化演示运行结果(C++/easyx)
gif图中,红色为广度优先搜索的路径,绿色为最终确立的最长有效路径,蓝色是可行走的路块。
其中,红色由起点蔓延至终点(探索),绿色由终点蔓延到起点(回溯)。