「ZJOI2015」幻想乡战略游戏

先考虑怎么高效地找到重心。

我们从根节点(令其为 \(1\))开始,每次往一个儿子走,要求以这个儿子为中心的的距离和更小(把式子写出来,容易知道这样的儿子要么没有,要么唯一),如果没有这样的儿子就结束。发现这个东西其实跟边权没有关系,因为写出来是一个 \(c(u,v)\times(s_1-2\times s_v)\) 的形式(\(s\) 是子树点权和),我们只关注它的正负,所以和边权无关。所以走的条件就是找一个满足 \(2\times s_v\ge s_1\) 的儿子 \(v\)

怎么做呢?相当于找到 dfs 序最大的 \(x\) 使得 \(2\times s_x \ge s_1\)。把 \(s\) 放在线段树上 dfs 序对应位置维护,一个节点维护区间 \(s\) 最大值,查询的时候直接线段树上二分即可。

找到重心后,求答案是显然的,每一条边的贡献是边权乘上子树内/外点权和,用两棵线段树维护,一棵专门维护边权乘以子树内和,另一棵维护边权乘以子树外和。相当于维护区间 \(\sum A_i\)\(\sum B_i\),操作是 \(A\) 的区间加,以及求区间 \(\sum A_i\times B_i\),是很经典的。

至此我们用 \(\mathcal O(n+q\log^2 n)\) 的时间复杂度解决了本题。


关于寻找重心,tx344 还阐述了一个更有意思(赤石,但是拓展性更强)的算法。

我们令 \(p_i\) 表示 dfs 序为 \(i\) 的节点编号,那么我们按如下步骤构造一个序列:先写下 \(s_{p_1}\)\(p_1\),然后 \(s_{p_2}\)\(p_2\),如此一直写到 \(s_{p_n}\)\(p_n\),观察发现这个序列的中位数(如果长度为偶数,则取左右两个都可)在重心的子树内。再从这个点出发向上二分。这个做法的优势在于,可以进行点权的子树加。如果使用一开始的方法,有两种截然不同的加法形式,可以求和,但是不便于求最值。对于这种方法,虽然还是线段树上二分,但我们要维护的是和,而非最值,所以把两个加法操作直接拼起来是没问题的,例题是 QOJ3307。时间复杂度与第一种方法是一样的。

posted @ 2025-02-11 22:19  TulipeNoire  阅读(26)  评论(0)    收藏  举报