1 条题解

  • 0
    @ 2025-11-4 8:20:16

    「迷失的字符串」题解

    问题分析

    给定一棵 nn 个节点的树,每条边上有一个小写字母。对于任意两个节点 u,vu,v,它们之间的唯一路径会形成一个字符串。现在有 mm 个查询字符串,需要判断每个查询字符串是否是某条路径形成的字符串。

    关键难点

    • 路径具有方向性:uvu \to vvuv \to u 产生不同的字符串
    • 数据规模大:n,m30000n, m \leq 30000,需要高效算法
    • 字符串总长度有限:所有查询字符串长度和 30000\leq 30000

    算法思路

    核心思想:点分治 + 哈希 + 字典树

    算法采用点分治框架,结合字符串哈希字典树来高效处理路径匹配问题。

    主要步骤

    1. 预处理

    • 构建字典树:将所有查询字符串插入字典树
    • 计算哈希值:为每个查询字符串计算前缀哈希和后缀哈希
    • 幂次预处理:计算基数 BB 的幂次,用于滚动哈希
    // 插入查询字符串到字典树
    ed[i] = tr.Add(s[i]);
    
    // 计算后缀哈希
    suf[i][s[i].size() - 1] = s[i][(int)s[i].size() - 1] - 'a' + 1;
    per(j, s[i].size() - 2, 0)
        suf[i][j] = (suf[i][j + 1] + 1ll * (s[i][j] - 'a' + 1) * bpw[(int)s[i].size() - 1 - j] % mo) % mo;
    
    // 计算前缀哈希  
    pre[i][0] = s[i][0] - 'a' + 1;
    re(j, s[i].size() - 1)
        pre[i][j] = (pre[i][j - 1] + 1ll * (s[i][j] - 'a' + 1) * bpw[j] % mo) % mo;
    

    2. 点分治框架

    void Div(int f_) {
        // 1. 计算子树大小,找到重心
        Dfs1(f_, 0);
        
        // 2. 如果子树很小,直接暴力匹配
        if (use.size() <= lim) {
            for (auto f : use) Dfs2(f, 0, 1);
            Clear();
            return;
        }
        
        // 3. 找到重心作为根节点
        int rt = f_;
        for (auto x : use)
            if (mx[x] < mx[rt]) rt = x;
        
        // 4. 从重心出发,收集所有路径的哈希值
        buk.clear();
        buk[0] = -1;
        nxt(i, rt, g) {
            int t = g.e[i].t, c = g.e[i].c - 'a' + 1;
            if (ban[t]) continue;
            Dfs3(t, rt, t, c);  // 收集哈希值
        }
        
        // 5. 检查查询字符串
        re(i, m) {
            if (Ans[i]) continue;
            // 检查完整路径匹配
            if (buk.count(pre[i][(int)s[i].size() - 1])) {
                Ans[i] = 1; continue;
            }
            if (buk.count(suf[i][0])) {
                Ans[i] = 1; continue;
            }
            // 检查路径经过重心的匹配
            rep(j, 0, (int)s[i].size() - 2) {
                if (buk.count(pre[i][j]) && buk.count(suf[i][j + 1])) {
                    int x = buk[pre[i][j]], y = buk[suf[i][j + 1]];
                    if (x != y || x == -1) {
                        Ans[i] = 1; break;
                    }
                }
            }
        }
        
        // 6. 递归处理子树
        Clear();
        ban[rt] = 1;
        nxt(i, rt, g) {
            int t = g.e[i].t;
            if (ban[t]) continue;
            Div(t);
        }
    }
    

    3. 关键函数说明

    Dfs1 - 计算子树大小

    计算每个节点的子树大小,用于寻找重心。

    Dfs2 - 暴力匹配(小子树)

    对于小的子树,直接从每个节点出发在字典树中匹配。

    Dfs3 - 收集路径哈希值

    从重心出发,收集所有子树的路径哈希值,并记录该路径来自哪个子树。

    void Dfs3(int f, int fa, int pa, int ha) {
        if (buk.count(ha) && buk[ha] != pa)
            buk[ha] = -1;  // 标记为来自多个子树
        else
            buk[ha] = pa;  // 记录路径来自哪个子树
        
        // 继续DFS收集
        nxt(i, f, g) {
            int t = g.e[i].t, c = g.e[i].c - 'a' + 1;
            if (t == fa) continue;
            if (ban[t]) continue;
            Dfs3(t, f, pa, (1ll * ha * B % mo + c) % mo);
        }
    }
    

    算法原理

    点分治的优势

    • 平衡树结构:每次将问题分解为规模减半的子问题
    • 路径分类:将所有路径分为三类:
      1. 完全在某个子树内
      2. 经过当前重心
      3. 跨越不同子树

    哈希匹配策略

    对于查询字符串 ss,检查三种情况:

    1. 完整正向匹配ss 是从重心到某个节点的路径
    2. 完整反向匹配ss 是从某个节点到重心的路径
    3. 分割匹配ss 的前缀匹配重心到节点 uu 的路径,后缀匹配重心到节点 vv 的路径,且 uvu \neq v

    小子树优化

    当子树节点数 100\leq 100 时,直接使用字典树暴力匹配,避免过度分解。

    复杂度分析

    • 字典树构建O(总字符串长度)O(\text{总字符串长度})
    • 点分治层数O(logn)O(\log n)
    • 每层处理O(n+查询处理)O(n + \text{查询处理})
    • 总复杂度O(nlogn+总字符串长度)O(n \log n + \text{总字符串长度})

    正确性保证

    1. 完备性:点分治确保考虑所有可能的路径
    2. 哈希准确性:使用模数 109+2110^9+21 和基数 2929 减少碰撞
    3. 方向处理:分别处理正向和反向路径
    4. 边界情况:小子树暴力确保正确性

    样例分析

    对于样例:

    4
    1 2 b
    1 4 a  
    2 3 c
    

    字符串 "bc" 匹配路径 232 \to 3"cb" 匹配路径 323 \to 2"abc" 匹配路径 41234 \to 1 \to 2 \to 3

    总结

    本题通过巧妙的点分治框架,结合字符串哈希和字典树,高效解决了树上路径字符串匹配问题。算法充分利用了问题的特殊性质:

    • 点分治分解问题规模
    • 哈希快速检查匹配
    • 字典树处理小规模情况
    • 精心设计的匹配策略处理路径方向性

    该解法在时间复杂度和实现复杂度之间取得了良好平衡,能够处理题目要求的大数据规模。

    • 1

    信息

    ID
    4896
    时间
    1000ms
    内存
    256MiB
    难度
    10
    标签
    递交数
    2
    已通过
    1
    上传者