在CCC单词搜索游戏中,单词可以隐藏在字母网格中,以直线或直角的方式排列。以下是对代码的详细注释和解题思路的总结:
传送门: https://www.luogu.com.cn/problem/P9303
代码注释
cpp
#include <iostream>
#include <vector>
#include <string>
using namespace std;
// 定义八个可能的搜索方向
const int dx[8] = {-1,-1, 0, 1, 1, 1, 0,-1}; // 北、东北、东、东南、南、西南、西、西北
const int dy[8] = { 0, 1, 1, 1, 0,-1,-1,-1};
int R, C; // 网格行数和列数
vector<string> grid; // 字母网格
string word; // 搜索区域分词
int word_len; // 单词长度
// 检查坐标是否在网格范围内
bool inBounds(int x, int y) {
return x >= 0 && x < R && y >= 0 && y < C;
}
// 检查单词是否可以沿指定方向的直线排列
bool checkStraight(int x, int y, int dir) {
for (int i = 0; i < word_len; ++i) {
int nx = x + dx[dir] * i; // 计算下一个位置的行坐标
int ny = y + dy[dir] * i; // 计算下一个位置的列坐标
if (!inBounds(nx, ny) || grid[nx][ny] != word[i]) // 如果超出边界或字母不匹配
return false; // 返回不匹配
}
return true; // 单词完全匹配
}
// 检查单词是否可以以L形排列(直角)
bool checkLShape(int x, int y, int dir1, int dir2, int split) {
// 检查第一段(从起点到分割点)
for (int i = 0; i < split; ++i) {
int nx = x + dx[dir1] * i;
int ny = y + dy[dir1] * i;
if (!inBounds(nx, ny) || grid[nx][ny] != word[i])
return false;
}
// 从分割点开始,沿新的方向检查剩余部分
int lx = x + dx[dir1] * (split - 1);
int ly = y + dy[dir1] * (split - 1);
for (int i = split; i < word_len; ++i) {
lx += dx[dir2]; // 更新行坐标
ly += dy[dir2]; // 更新列坐标
if (!inBounds(lx, ly) || grid[lx][ly] != word[i])
return false;
}
return true; // 单词完全匹配
}
// 判断两个方向是否垂直
bool arePerpendicular(int dir1, int dir2) {
return (dx[dir1] * dx[dir2] + dy[dir1] * dy[dir2]) == 0; // 点积为0则垂直
}
int main() {
cin >> word;
word_len = word.length();
cin >> R >> C;
grid.resize(R);
// 读取网格
for (int i = 0; i < R; ++i) {
grid[i] = "";
for (int j = 0; j < C; ++j) {
string ch;
cin >> ch;
grid[i] += ch;
}
}
int count = 0;
// 遍历每个单元格作为起点
for (int x = 0; x < R; ++x) {
for (int y = 0; y < C; ++y) {
if (grid[x][y] != word[0]) continue; // 跳过不匹配首字母的单元格
// 检查所有可能的直线排列
for (int d = 0; d < 8; ++d) {
if (checkStraight(x, y, d))
count++;
}
// 检查所有可能的L形排列
for (int d1 = 0; d1 < 8; ++d1) {
for (int d2 = 0; d2 < 8; ++d2) {
if (d1 == d2 || !arePerpendicular(d1, d2))
continue; // 跳过相同方向或非垂直方向
for (int split = 2; split < word_len; ++split) { // 分割点至少在第二个字母
if (checkLShape(x, y, d1, d2, split))
count++;
}
}
}
}
}
cout << count << endl; // 输出匹配次数
return 0;
}
解题思路总结
- 输入读取:读取单词、网格的行数和列数,以及网格本身。
- 方向定义:定义八个可能的搜索方向,涵盖所有直线方向。
- 边界检查:确保坐标在网格范围内。
- 直线匹配:对于每个起点,检查是否可以沿八个方向之一形成直线排列。
- 直角匹配:对于每个起点,检查是否可以形成L形排列,确保两个方向垂直。
- 计数:统计所有可能的匹配方式,并输出总次数。
代码优点
- 明确的方向定义:八个方向涵盖了所有可能的直线搜索方向。
- 高效的边界检查:确保搜索过程中不越界。
- 独立的匹配检查:直线和L形匹配的逻辑分离,代码结构清晰。
- 垂直方向判断:通过点积判断两个方向是否垂直,数学上简洁有效。
代码缺点
- 性能问题:对于较长的单词或较大的网格,递归搜索可能导致性能下降。
- 重复计算:某些情况下可能会重复检查相同的路径。
- 代码复杂度:涉及多个嵌套循环,可能影响可读性。
总结
该代码实现了对字母网格中单词的搜索,能够处理单词以直线或直角方式排列的情况。通过详细的注释和清晰的解题思路,代码易于理解和维护。