P9584 题解

· · 题解

题目传送门

好不容易在赛时写出来了,特此纪念。(小号)

这题的大数据是真的有用。

换根 dp 模板题。

【前言】

前置知识:换根 dp。

建议先行完成换根 dp 模板题以后再来做本题。

话说普及组为什么会有换根 dp?

【具体思路】

看完题,立马能想到全源最短路。可是即使用 spfa 迪杰斯特拉算法、Johnson 算法都会超时。毕竟这个 n \le 2 \times 10^5O(n^2) 都过不去。(也许能得 50 分)。

既然求最短路的算法不行,该考虑玄学优化从题目限制中找线索了。

注意到题目中的一句话:

n 个城市之间由 n-1 条双向道路互相连接。保证从任意一个城市出发,都能通过这 n-1 条双向道路,到达任意一个城市。

树上的“全源最短路”怎么求呢?立马想到换根 dp,时间复杂度仅为 $O(n)$。 由于这是一颗无根树,因此默认根为 $1$。 换根 dp 的思路在前面的模板题的题解里面,可以看看,这里就只讲一点。 以下没有图,大家自行脑部,推荐画出来。 设 $dp_i$ 为编号是 $i$ 的点到其它点的路径长度总和,$ds_i$ 为根节点到编号为 $i$ 的节点的路径长度。 首先,有一棵树,我们可以通过 $O(n)$ 的时间求出(即遍历一遍树)编号为 $1$。 此时,$dp_1$ 已经被求出来了,即为 $\sum\limits_{i=1}^{n} ds_i$。 至于换根 dp,又是什么呢? 比如说,节点 $u$ 有一个子节点 $v$,则将根转移到 $v$ 时(其实就是统计这个子节点到各个节点路径长度总和),以 $v$ 为根的子树到根节点的距离减少了 $cost(u,v)$,其它节点到根节点的距离就增加了 $cost(u,v)$。 再预处理出 $size_i$ 为以 $1$ 号节点为根时 $i$ 节点为根的子树有几个节点。 易得状态转移方程为:$dp_v=dp_u-size_v \times cost(u,v)+size_u \times cost(u,v)$。 以此类推,直到推完所有节点。 经过换根 dp 之后,我们可以将各个节点到其它节点路径长度总和求出。 这样,预处理就结束了。 而对于每一个问题,节点 $n+1$ 到其它节点的路径之和易得出是 $dp_{k_i}+w_in$。可以看成从节点 $n+1$ 走到节点 $k_i$ ,再走到各个点。 至于其它点到节点 $n+1$ 呢?其实也是上面那个式子,因为现在节点数量为 $n+1$,边为 $n$,图还是连通图,因此现在的图还是一棵树。 因此,对于每一次询问,都可以得出结果为 $\sum\limits_{i=1}^{n} dp_i+2 \times (dp_{k_i}+w_in)$。 若还是不太懂,看看具体是如何实现的。 **【具体实现】** 第一步:输入&建图。 这一步没什么好说的,用链式前向星或者 ```vector``` 都可以建图(个人喜欢用 ```vector```)。**注意建双向边。** ```cpp n=read(); q=read(); for(int i=1;i<n;i++){ int u,v,w; u=read(); v=read(); w=read(); vec[u].push_back((node){v,w}); vec[v].push_back((node){u,w}); } ``` 本人用了快读快输。 第二步:换根 dp(二次扫描法)。 首先是处理 $size$ 数组以及处理 $ds$ 数组,具体见代码。 ```cpp void dfs(int u){ si[u]=1;//大小当然初始化为 1(根节点) for(int i=0;i<vec[u].size();i++){//枚举每一个子节点 //获取这条边的数据。 int z=vec[u][i].zhong; int c=vec[u][i].chang; //如果访问过了打断 if(flag[z]){ continue; } //标记一下 flag[z]=1; //更新数组 ds[z]=ds[u]+c; //搜索子节点 dfs(z); //处理 size si[u]+=si[z]; } } ``` 然后是第二次遍历,大体思路前面已经讲过了,给出的代码主要是细节实现。 ```cpp void dfn(int u){ for(int i=0;i<vec[u].size();i++){//枚举每个子结点 //获取子节点数据 int z=vec[u][i].zhong; int c=vec[u][i].chang; //访问过了就不访问了 if(flag[z]){ continue; } //标记 flag[z]=1; //转移 dp[z]=dp[u]-si[z]*c+(n-si[z])*c; //搜索子节点 dfn(z); } } ``` 第三步:统计答案。 答案比较好统计,上面已经给出。只要注意**取模**就可以了。 **【最后总结】** [完整代码](https://www.luogu.com.cn/paste/9roxml1g)。 [提交记录](https://www.luogu.com.cn/record/122958797)。 本题虽然涉及到提高组算法,但是其难度并不算太高。不过这种题目在 ```CSP-J``` 中出现的概率应该很小。可以说,这题算是 ```S``` 组 ```T1``` 的题,是为提高组选手准备的。(以上仅个人观点) 如果还有不懂的可以私信我。