Problem Description
有一个M * N的材料和一个s * t的模板,从材料中切除模板,求最大能切出来的模板的数量。
Input
第一行输入材料的大小M,N。
接下来的M行N列输入材料所组成的字符阵列,如样例所示。
下面输入模板的大小s,t。
接下来的s行t列输入模板所组成的字符阵列,如样例所示。
Output
可裁剪下来的模板的数量。
Sample Input
3 4
a b c d
c d a b
a c c d
2 2
a b
c d
Sample Output
2
解题思路
- 以模板左上角为坐标点,对材料每个可裁剪模板的位置进行模板匹配。
- 采用深度优先搜索,搜索所有裁剪方案,得出最大可裁剪模板数量。
经验总结
- 深度优先搜索就是对材料每个可与模板匹配的点做是否裁剪的选择问题,选择裁剪之后,遍历完所有点得到已经裁剪的模板数量;再走不选择裁剪的路(此时需要将材料裁剪的状况回到未裁剪的状态),在遍历的过程中更新最大可裁剪的模板数量。
- 某一行的每一列(可裁剪)都遍历完后,将行数下移时,传入dfs的列数(y值)应为0。
- 字符和数字一起录入时,需注意要用getchar()吸收空格和换行符,或者直接写在scanf()格式里。
代码实现(C)
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#define MaxSize 10000
char material[MaxSize][MaxSize]; // 材料
char template[MaxSize][MaxSize]; // 模板
bool used[MaxSize][MaxSize]; // 材料位置是否被裁剪
int M, N, s, t; // 材料和模板的长宽
int max; // 最大可裁剪的模板数量
// 录入数据
void input(int a, int b, char A[][MaxSize]) {
for (int i = 0; i < a; ++i) {
for (int j = 0; j < b; ++j) {
scanf("%c", &A[i][j]);
getchar(); // 吸收空格和换行符
}
}
}
// 判断材料是否与模板匹配
bool isMatch(int x, int y) { // (x,y)为左上角顶点
for (int k = 0; k < s; ++k) // x~x+s
for (int l = 0; l < t; ++l) // y~y+t
if (material[x + k][y + l] != template[k][l] || used[x + k][y + l]) // 不匹配或已裁剪
return false;
return true;
}
// 更改材料的裁剪状态
void isUsed(int x, int y, bool b) {
for (int k = 0; k < s; ++k)
for (int l = 0; l < t; ++l)
used[x + k][y + l] = b;
}
// 深度优先搜索全部裁剪路径,得出裁剪模板最多的数量
void dfs(int x, int y, int sum) { // 传入当前所在位置,以及已裁剪的模板数量
if (sum > max) // 更新最大可裁剪的模板数量
max = sum;
if (x > M - s) // 递归边界:x到达材料可裁剪模板的最后一行(M-s)的下一行(M-s+1)
return;
for (int j = y; j <= N - t; ++j) { // 对x行的每一列(可裁剪)进行搜索
if (isMatch(x, j)) { // 若当前位置材料与模板匹配
isUsed(x, j, true); // 将当前材料位置裁剪
dfs(x, j + t, sum + 1); // 选择裁剪,继续搜索
isUsed(x, j, false); // 到这里即不选择裁剪,将材料状态改回(回溯)
}
}
dfs(x + 1, 0, sum); // 搜索下一行
}
int main() {
while (~scanf("%d %d", &M, &N)) {
// 初始化max和used数组
max = 0;
memset(used, false, sizeof(used));
getchar(); // 吸收换行符
input(M, N, material);
scanf("%d %d", &s, &t);
getchar(); // 吸收换行符
input(s, t, template);
dfs(0, 0, 0);
printf("%d\n", max);
}
return 0;
}