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\)。那么每个点只会被虚点遍历一次,一次后可以直接把它删掉,时间复杂度就有了保证。
维护一个点是否被删,我们可以用并查集维护,父节点为第一个没有被删的点,删除这个点标记一下,然后把它的儿子和它的父亲连边即可。
查询的时候跳深度更大的节点即可。
那么我们完成了对于虚点找实点的操作。
现在来思考实点如何快速找虚点。
发现有两种情况:
- 实点 \(x\) 是 \(u\) 链的 \(LCA\)。这个用 vector 维护即可
- \(u\) 链的一端在 \(x\) 的子树内,一个点在子树外。即一端的 dfs 序在 \(L[x]\sim R[x]\) 中,另一端不在。我们需要维护一个端点在 \(L[x]\sim R[x]\) 中另一个端点的最大值/最小值,此时用线段树维护 dfs 序区间的信息,叶子处用 set 维护最大值/最小值即可(方便删除)。
具体实现的时候把普通边和特殊边一起放进线段树里,找跨过 \(u\) 子树的边和从 \(u\) 出发的边即可。
总时间复杂度 \(\mathcal{O}((n+m)\log_2 n)\)。
 
                     
                    
                 
                    
                
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号