UVa 1127 Word Puzzles

题目分析

问题描述

我们有一个 L×CL \times CL×C 的字母矩阵,以及 WWW 个需要查找的单词。每个单词在矩阵中会以直线方向出现(共 888 个可能方向)。需要找出每个单词在矩阵中第一次出现的起始位置和方向。

方向编码规则

题目使用字母 A\texttt{A}AH\texttt{H}H 表示 888 个方向,从正北开始顺时针排列:

  • A\texttt{A}A: 北 (−1,0)(-1, 0)(1,0)
  • B\texttt{B}B: 东北 (−1,+1)(-1, +1)(1,+1)
  • C\texttt{C}C: 东 (0,+1)(0, +1)(0,+1)
  • D\texttt{D}D: 东南 (+1,+1)(+1, +1)(+1,+1)
  • E\texttt{E}E: 南 (+1,0)(+1, 0)(+1,0)
  • F\texttt{F}F: 西南 (+1,−1)(+1, -1)(+1,1)
  • G\texttt{G}G: 西 (0,−1)(0, -1)(0,1)
  • H\texttt{H}H: 西北 (−1,−1)(-1, -1)(1,1)

输入输出格式

输入格式

  • 第一行:测试用例数
  • 每个测试用例:
    • 第一行:LLL CCC WWW(行数、列数、单词数)
    • 接下来 LLL 行:字母矩阵
    • 接下来 WWW 行:要查找的单词

输出格式

  • 每个单词一行:起始行 起始列 方向字母
  • 测试用例之间用空行分隔

数据范围

  • 0<L≤10000 < L \leq 10000<L1000
  • 0<C≤10000 < C \leq 10000<C1000
  • 0<W≤10000 < W \leq 10000<W1000

解题思路

暴力搜索的局限性

最直接的解法是从矩阵的每个位置出发,向 888 个方向尝试匹配每个单词。这种暴力方法的时间复杂度为 O(L×C×W×len×8)O(L \times C \times W \times \texttt{len} \times 8)O(L×C×W×len×8),在数据范围上限时无法通过。

优化方案:Trie\texttt{Trie}Trie 树 + 方向搜索

我们采用**Trie\texttt{Trie}Trie 树(字典树)**来优化搜索过程:

  1. 构建 Trie\texttt{Trie}Trie:将所有待查找单词插入Trie\texttt{Trie}Trie 树中,每个叶子节点记录对应单词的索引。

  2. 矩阵搜索:从矩阵的每个位置 (r,c)(r, c)(r,c) 出发,向 888 个方向在 Trie\texttt{Trie}Trie 树上行走:

    • 沿着当前方向取字符
    • Trie\texttt{Trie}Trie 树上移动对应分支
    • 如果到达单词结尾节点,记录答案
  3. 提前终止:一旦所有单词都找到,立即停止搜索。

算法优势

  • 时间复杂度O(L×C×8×maxLength)O(L \times C \times 8 \times \text{maxLength})O(L×C×8×maxLength),其中 maxLength\text{maxLength}maxLength 是最长单词长度
  • 空间复杂度O(总单词长度×26)O(\text{总单词长度} \times 26)O(总单词长度×26)
  • 实际效率:比暴力搜索快很多,因为 Trie\texttt{Trie}Trie 树能快速排除不匹配的分支

代码实现

// 1127
// UVa ID: Word Puzzles
// Verdict: Accepted
// Submission Date: 2025-10-16
// UVa Run Time: 0.300s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net

#include <iostream>
#include <vector>
#include <string>
#include <cstring>
#include <tuple>
using namespace std;

const int MAXN = 1000;
const int ALPHABET = 26;

// 8个方向:北、东北、东、东南、南、西南、西、西北
int dr[8] = {-1, -1, 0, 1, 1, 1, 0, -1};
int dc[8] = {0, 1, 1, 1, 0, -1, -1, -1};
char dirChar[8] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'};

// Trie树节点结构
struct TrieNode {
    int wordIndex; // -1表示不是单词结尾,否则存储单词在words中的下标
    TrieNode* children[ALPHABET];
    TrieNode() : wordIndex(-1) {
        memset(children, 0, sizeof(children));
    }
};

// 向Trie树中插入单词
void insertWord(TrieNode* root, const string& word, int index) {
    TrieNode* node = root;
    for (char ch : word) {
        int idx = ch - 'A';
        if (!node->children[idx]) {
            node->children[idx] = new TrieNode();
        }
        node = node->children[idx];
    }
    node->wordIndex = index;
}

void solve() {
    int L, C, W;
    cin >> L >> C >> W;
    
    vector<string> grid(L);
    for (int i = 0; i < L; i++) {
        cin >> grid[i];
    }
    
    vector<string> words(W);
    for (int i = 0; i < W; i++) {
        cin >> words[i];
    }
    
    // 构建Trie树
    TrieNode* root = new TrieNode();
    for (int i = 0; i < W; i++) {
        insertWord(root, words[i], i);
    }
    
    // 存储每个单词的答案,初始化为未找到状态
    vector<tuple<int, int, char>> ans(W);
    for (int i = 0; i < W; i++) {
        ans[i] = make_tuple(-1, -1, ' ');
    }
    
    int foundCount = 0; // 已找到的单词数量,用于提前终止
    
    // 从每个位置每个方向在Trie树上搜索
    for (int r = 0; r < L && foundCount < W; r++) {
        for (int c = 0; c < C && foundCount < W; c++) {
            for (int dir = 0; dir < 8 && foundCount < W; dir++) {
                TrieNode* node = root;
                int nr = r, nc = c;
                // 沿着当前方向在Trie树上行走
                while (nr >= 0 && nr < L && nc >= 0 && nc < C && node) {
                    char ch = grid[nr][nc];
                    int idx = ch - 'A';
                    node = node->children[idx];
                    if (!node) break; // Trie树上无此分支,提前结束
                    
                    // 如果找到一个单词
                    if (node->wordIndex != -1) {
                        int wi = node->wordIndex;
                        if (get<0>(ans[wi]) == -1) { // 只记录第一次出现
                            ans[wi] = make_tuple(r, c, dirChar[dir]);
                            foundCount++;
                        }
                    }
                    
                    nr += dr[dir];
                    nc += dc[dir];
                }
            }
        }
    }
    
    // 输出结果
    for (int i = 0; i < W; i++) {
        cout << get<0>(ans[i]) << " " << get<1>(ans[i]) << " " << get<2>(ans[i]) << endl;
    }
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    
    int t;
    cin >> t;
    
    for (int i = 0; i < t; i++) {
        if (i > 0) cout << endl;
        solve();
    }
    
    return 0;
}

关键点说明

  1. Trie树构建:将单词按字符顺序插入,形成前缀树结构。

  2. 方向处理:使用方向数组 dr\texttt{dr}drdc\texttt{dc}dc 统一处理 888 个方向的移动。

  3. 提前终止:通过 foundCount\texttt{foundCount}foundCount 变量在所有单词找到后立即停止搜索,大幅提升效率。

  4. 只记录第一次出现:使用 -1\texttt{-1}-1 初始值确保只记录每个单词的第一次出现位置。

该算法充分利用了Trie树的前缀匹配特性,避免了大量不必要的比较,是解决此类单词搜索问题的高效方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值