BZOJ4699-树上的最短路 题解

BZOJ4699-树上的最短路

题意

一个 \(n\) 个点的无根无向树,有 \(m\) 条特殊有向边 \((u1\to v1)\to(u2\to v2)\)\(u1\to v1\)\(u1\)\(v1\) 的最短路径上的所有点,\(u2\to v2\) 同理),每条边均有边权 \(w_x\),求每个点到点 \(k\) 的最短路长度。

\(1\le n\le 2.5\times 10^5,1\le m\le10^5,w\le 10^9,1\le k\le n\)

思路

显然我们可以将特殊有向边反向,然后求 \(k\) 为起点的单源最短路。

发现 dij 的复杂度瓶颈应该是在特殊边,暴力连边是 \(\mathcal{O}(n^2\times m)\) 的,所以我们考虑什么时候才会用到这个特殊边,发现在 dij 中 \(dis\) 是单调不降的,且特殊边的 \(u\) 集合的任一点可以到 \(v\) 集合的任一点,所以我们一定是在第一次取出 \(u\) 集合中点的时候用这条特殊边,所以用一次后可以直接删掉。

但这样也会让一些点疯狂的更新,带来大量的时间开销,我们对每一条特殊边建一个虚点,点集 \(u\) 连向虚点权值为 \(w\),虚点连向 \(v\) 权值为 \(0\),这样第一次被虚点遍历到的点选择这个虚点就一定是最优的虚点,因为 dij 先取 \(dis\) 小的,并且它们的边权都是 \(0\)。那么每个点只会被虚点遍历一次,一次后可以直接把它删掉,时间复杂度就有了保证。

维护一个点是否被删,我们可以用并查集维护,父节点为第一个没有被删的点,删除这个点标记一下,然后把它的儿子和它的父亲连边即可。

查询的时候跳深度更大的节点即可。

那么我们完成了对于虚点找实点的操作。

现在来思考实点如何快速找虚点。

发现有两种情况:

  1. 实点 \(x\)\(u\) 链的 \(LCA\)。这个用 vector 维护即可
  2. \(u\) 链的一端在 \(x\) 的子树内,一个点在子树外。即一端的 dfs 序在 \(L[x]\sim R[x]\) 中,另一端不在。我们需要维护一个端点在 \(L[x]\sim R[x]\) 中另一个端点的最大值/最小值,此时用线段树维护 dfs 序区间的信息,叶子处用 set 维护最大值/最小值即可(方便删除)。

具体实现的时候把普通边和特殊边一起放进线段树里,找跨过 \(u\) 子树的边和从 \(u\) 出发的边即可。

总时间复杂度 \(\mathcal{O}((n+m)\log_2 n)\)

posted @ 2025-04-11 14:58  Qing_Nian  阅读(66)  评论(0)    收藏  举报