树结构中q次最短路径查询后求访问量最高节点的最优算法咨询
解决树路径查询访问次数最多节点的最优算法
嗨,我完全懂你遇到的困境——直接用BFS遍历每条查询路径在数据量到1e5的时候肯定会超时,毕竟单次查询的路径长度最坏能到O(n),总操作数会飙升到O(q*n),这完全扛不住。这里有个O(n log n + q log n)的最优解法,核心是用差分思想结合LCA(最近公共祖先),绝对能搞定1e5级别的规模。
核心思路
树中任意两点s到t的最短路径,都可以拆成s -> LCA(s,t)和t -> LCA(s,t)(注意LCA只被统计一次)。我们可以用差分数组批量标记所有查询的路径,最后通过一次树的遍历统计每个节点的实际访问次数,不用逐个遍历每条路径。
具体步骤
1. 预处理LCA(倍增法)
首先得预处理每个节点的最近公共祖先,这样每次查询能在O(log n)时间内得到s和t的LCA。倍增法是最常用的高效方式:
- 预处理每个节点的深度
depth[u],以及up[k][u](表示u的2^k级祖先) - 预处理的时间复杂度是O(n log n),单次LCA查询的时间是O(log n)
2. 初始化差分数组
创建一个大小为n+1的数组cnt,初始值全为0。这个数组用来记录路径的差分标记。
3. 处理每一次查询
对于每个查询的源节点s和目标节点t:
- 计算
l = LCA(s, t) - 执行以下差分操作:
cnt[s] += 1 cnt[t] += 1 cnt[l] -= 1 if parent[l] exists: cnt[parent[l]] -= 1
这么做的逻辑是:
cnt[s] +=1标记从s到根的路径整体加1cnt[t] +=1标记从t到根的路径整体加1cnt[l] -=1和cnt[parent[l]] -=1是减去两次从l到根的路径(因为s到根和t到根的路径在l到根部分重复了),同时保证l本身只被计数一次。
4. 后序遍历统计实际访问次数
通过一次后序遍历(迭代式DFS更稳妥,避免递归栈溢出),对于每个节点u,将所有子节点的cnt值累加到u的cnt上。操作完成后,cnt[u]就是节点u在所有查询中被访问的总次数。
5. 找出访问次数最多的节点
遍历所有节点,找到cnt值最大的那个节点即可(如果有多个,任选其一都满足要求)。
复杂度分析
- LCA预处理:O(n log n)
- 处理q次查询:O(q log n)
- 后序遍历统计:O(n)
总复杂度是O(n log n + q log n),完全能处理n和q都为1e5的场景。
为什么BFS会超时?
BFS遍历每条路径的方式,最坏情况下每次查询要走O(n)个节点,q=1e5时总操作数会达到1e10,远远超出时间限制。而上面的方法把路径标记转化为O(1)的差分操作,最后用一次遍历统计,效率提升了几个数量级。
实现细节提醒
- 递归DFS处理1e5节点可能会栈溢出,建议用迭代式DFS或者逆序BFS来做后序遍历。
- 倍增法的预处理要注意数组大小,比如
up数组的维度可以设为[20][n+1](因为2^20已经超过1e5)。
内容的提问来源于stack exchange,提问作者joydip panda
相关产品推荐
相关产品推荐

