1 条题解
-
0
算法标签
- 树哈希(Tree Hashing)
- 深度优先搜索(DFS)
- 最近公共祖先(LCA)
- 字符串处理
- 树形动态规划
分析
问题理解
题目要求在一个树形结构(n个节点,n-1条边)中,找到所有满足特定条件的路径字符串,这些字符串被称为"模板"。
模板的定义:如果城市中所有产生相同字符串的路径的并集能够覆盖树中的每条边至少一次,那么这个字符串就是模板。
核心思路
1. 数据预处理
// 构建邻接表存储树结构 vector<pair<int, char>> G[N];2. 树哈希计算(关键步骤)
void DFS1(int u, int f, ull hsh) { if (idx[S].find(hsh) == idx[S].end()) idx[S][hsh] = ++ len[S]; vec[S][idx[S][hsh]].push_back(u); for (auto [v, c] : G[u]) if (v != f) DFS1(v, u, (hsh * P + c) % MOD); }- 对每个节点S作为起点,计算从S出发的所有路径的哈希值
- 使用多项式哈希:
hsh = (hsh * P + c) % MOD - 相同哈希值的路径对应相同的字符串
3. LCA预处理
void DFS2(int u, int f) { depth[u] = depth[f] + 1, fa[u] = f; for (auto [v, c] : G[u]) if (v != f) DFS2(v, u); } int LCA(int u, int v) { // 递归计算最近公共祖先 }4. 模板验证
这是算法的核心逻辑:
// 对于每个哈希值(对应一个字符串) for (auto [hsh, o] : idx[S]) { // 重置统计数组 for (int i = 1; i <= n; i ++) sum[i] = 0; // 统计所有产生该字符串的路径 for (int i = 1; i <= n; i ++) if (idx[i].find(hsh) != idx[i].end()) { int t = idx[i][hsh]; for (int j : vec[i][t]) sum[i] ++, sum[j] ++, sum[LCA(i, j)] -= 2; } // 树形DP统计边覆盖情况 DFS3(1, 0); // 检查是否所有边都被覆盖 bool flag = true; for (int i = 2; i <= n; i ++) flag &= (sum[i] != 0); if (flag) res[vec[S][o][0]] = 1; }验证原理:
- 使用差分数组技术在树上标记路径
sum[i]++和sum[j]++标记路径端点sum[LCA(i,j)] -= 2消除公共祖先的重复计数- 通过DFS3进行树形DP,
sum[u]最终表示经过边(u, fa[u])的路径数量 - 如果所有
sum[i]都不为0,说明所有边都被覆盖
5. 结果收集
void DFS4(int u, int f) { if (res[u]) { Ans.insert(cur); // 同时插入反向字符串(因为路径是双向的) string tmp; for (int i = (int)cur.size() - 1; i >= 0; i --) tmp.push_back(cur[i]); Ans.insert(tmp); } for (auto [v, c] : G[u]) if (v != f) { cur.push_back(c); DFS4(v, u); cur.pop_back(); } }算法复杂度
- 时间复杂度:O(n³) 最坏情况
- 外层循环:O(n)(选择起点S)
- 内层循环:O(n²)(LCA计算和路径统计)
- 空间复杂度:O(n²)
算法优化点
- 哈希碰撞处理:使用多项式哈希,但可能存在哈希碰撞
- LCA优化:可以使用倍增法或Tarjan算法优化LCA计算
- 重复计算:某些计算可以缓存优化
总结
这个解决方案的核心思想是:
- 枚举所有可能的路径字符串(通过树哈希)
- 验证每个字符串是否满足模板条件(通过树形差分统计边覆盖)
- 收集并输出所有模板字符串
算法巧妙地结合了哈希技术、树形结构和差分统计来解决这个复杂的组合优化问题。
- 1
信息
- ID
- 3812
- 时间
- 2000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者