BZOJ 4719: [Noip2016]天天爱跑步

O ( n 2 ) 做法

枚举每个起点,从该点开始 d f s ,把它到终点的路径上能观察到它的观察员数组下标 + 1


树的形态为链

枚举每个观察员,统计左右两边与它相距为 W j 的节点。

时间复杂度 O ( n )


所有的 S i = 1

1 为根,枚举每个观察员,发现只有它的 W j = d e e p [ j ] 时才会观察到玩家(设 d e e p [ 1 ] = 0 ),如果它的 W j = d e e p [ j ] ,就统计它的子树上有多少个节点会有玩家到达。预处理 f [ i ] 表示子树 i 的和即可。

时间复杂度 O ( n )


所有的 T i = 1
算法1

1 为根,对于每个观察员,只需统计有多少个玩家,起点的深度为 d e e p [ j ] + W j ,并且在它的子树中。

f [ i ] 表示深度为 i 的节点上的玩家数量,但如何保证在观察员的子树中这一条件?

线段树合并可以搞,叶子节点的下标 i 1 d e e p m a x ,记录的值为起点的深度为 i 的玩家的数量,从原树的底部向上依次合并。

时间复杂度 O ( n l o g n )

算法2

发现其实好像不用那么麻烦,下面有一个很很很巧妙的做法——

f [ i ] 表示起点的深度为 i 的玩家有多少个,初始时为空,从 1 号节点开始 d f s ,每访问到一个点 j ,就 + + f [ d e e p [ j ] ] ,访问到节点 j 时,记录一下 f [ d e e p [ j ] + W j ] 的大小,然后继续往下递归它的子树,递归回来后,再查看一下 f [ d e e p [ j ] + W j ] 的大小,看比原来多了多少,这个差值就是答案。

时间复杂度 O ( n )


满分做法

上面说了起点位于当前子树内的做法,下面再考虑终点位于当前子树内的?

算法1

还是用线段树合并。比上面多维护一个如下所述的 g 数组即可。

算法2

继续用 d f s 计算差值,外加一个容斥。

f 数组计算起点位于当前子树内的玩家个数,方法如 T i = 1 的算法2所述;

g 数组计算终点位于当前子树内的玩家个数:

设某个玩家的终点为 i 且在子树 j 中,起点到终点的长度为 L i ,则当 L i ( d e e p [ i ] d e e p [ j ] ) = W j 时,就要被统计到观察员 j 的观察数目中。这个式子可以转化成:

L i d e e p [ i ] = W j d e e p [ j ]

由于 L i 可能小于 d e e p [ i ] ,于是我们将等式两边同时加 n

任选一个根节点开始 d f s ,每访问到一个终点 i ,就 + + g [ L i d e e p [ i ] + n ] ,同时记录一下当前的 g [ W j d e e p [ j ] + n ] ,然后继续递归子树,递归回来后,计算一下 g [ W j d e e p [ j ] + n ] 多了多少,统计答案。

容易发现,如果起点和终点都在子树 j 中,会被误计入 j 的答案,最后再减掉即可。

我们在倍增计算 L i 时,就可以同时在 f a [ l c a ] + 1 ,表示有一个玩家的起点与终点都在 f a [ l c a ] 的子树内。

猜你喜欢

转载自blog.csdn.net/Milkyyyyy/article/details/81747799