1 条题解
-
0
「迷失的字符串」题解
问题分析
给定一棵 个节点的树,每条边上有一个小写字母。对于任意两个节点 ,它们之间的唯一路径会形成一个字符串。现在有 个查询字符串,需要判断每个查询字符串是否是某条路径形成的字符串。
关键难点:
- 路径具有方向性: 和 产生不同的字符串
- 数据规模大:,需要高效算法
- 字符串总长度有限:所有查询字符串长度和
算法思路
核心思想:点分治 + 哈希 + 字典树
算法采用点分治框架,结合字符串哈希和字典树来高效处理路径匹配问题。
主要步骤
1. 预处理
- 构建字典树:将所有查询字符串插入字典树
- 计算哈希值:为每个查询字符串计算前缀哈希和后缀哈希
- 幂次预处理:计算基数 的幂次,用于滚动哈希
// 插入查询字符串到字典树 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); } }算法原理
点分治的优势
- 平衡树结构:每次将问题分解为规模减半的子问题
- 路径分类:将所有路径分为三类:
- 完全在某个子树内
- 经过当前重心
- 跨越不同子树
哈希匹配策略
对于查询字符串 ,检查三种情况:
- 完整正向匹配: 是从重心到某个节点的路径
- 完整反向匹配: 是从某个节点到重心的路径
- 分割匹配: 的前缀匹配重心到节点 的路径,后缀匹配重心到节点 的路径,且
小子树优化
当子树节点数 时,直接使用字典树暴力匹配,避免过度分解。
复杂度分析
- 字典树构建:
- 点分治层数:
- 每层处理:
- 总复杂度:
正确性保证
- 完备性:点分治确保考虑所有可能的路径
- 哈希准确性:使用模数 和基数 减少碰撞
- 方向处理:分别处理正向和反向路径
- 边界情况:小子树暴力确保正确性
样例分析
对于样例:
4 1 2 b 1 4 a 2 3 c字符串
"bc"匹配路径 ,"cb"匹配路径 ,"abc"匹配路径 。总结
本题通过巧妙的点分治框架,结合字符串哈希和字典树,高效解决了树上路径字符串匹配问题。算法充分利用了问题的特殊性质:
- 点分治分解问题规模
- 哈希快速检查匹配
- 字典树处理小规模情况
- 精心设计的匹配策略处理路径方向性
该解法在时间复杂度和实现复杂度之间取得了良好平衡,能够处理题目要求的大数据规模。
- 1
信息
- ID
- 4896
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 2
- 已通过
- 1
- 上传者