[NOIP2016] 天天爱跑步 (LCA+线段树(动态开点)+差分+dfs序)
做完这题,好哈皮,一下子填了好多坑(坑太多= =),说实话这道题我觉得真的很有水平,如果我考的话应该会默默敲暴力
(暴露蒟蒻本性)。。。话不多说来看题吧。
不得不说,出题人很良心嘛,数据范围给的这么清楚(人家只不过知道你想不出正解罢了)= =
如果按照每个player的路径算观察者肯定是不行的(不信你试试,不过好像有20分(裸暴力))
那我们换一种考虑方式对于每一个观察者(我们假设S为起点,T为终点),S为多少的player才会对他产生贡献(被观察到)。
思考两种情况: 因为是在树上,所以肯定有向上走和向下走两种情况,两种情况是类似的,我们以向上走为例。
不难得出一个式子: 对于i号点的观察员 当存在一个player使得 dep[S]-tim[i]=dep[i] 时,该观察员可以观察到他。
移项可得:dep[S]=tim[i]+dep[i].
观察此式 左边仅与player有关,而右边只与观察者有关,那么我们可以得到这样一个思路
每一个深度建一棵线段树,把所有player的起点插入到以dep[S]为根的线段树中,对于每一个观察者向上走能经过他的,
一定在他的子树中,所以我们在以深度tim[i]+dep[i]为根的线段树中查询他所能控制到的左右区间(dfs序)
但是如果直接这样做是不行的,考虑一种情况,如下图(自己画的,多多见谅)
红色的S的确是在观察员i的子树中,如果直接按上文说的查询i的子树,S会被算在内,但明显可以看出S并不会从i经过
怎么办?这就需要差分了(树上差分)在LCA(S,T)的父节点减1,这样就不会把它算在内了,(不理解自己画一画)
这样的话,整体思路就出来了,一遍dfs跑出深度,dfs序,因为要求LCA,倍增又很快,就顺带求出fa
求出每一个player的LCA(S与T的),然后在以深度dep[S]为根的线段树中,S的位置+1,fa[LCA]的位置-1
然后对于每一个节点(即观察员),在以深度tim[i]+dep[i]为根的线段树中查询他所能控制到的左右区间的sum即可
简单说一下向下走
同样可得一个式子:dep[S]+dep[i]-2*dep[LCA(S,T)]=tim[i]
移项可得:dep[S]-2*dep[LCA(S,T)]=tim[i]-dep[i]
同理,在以深度dep[S]-2*dep[LCA(S,T)]为根的线段树中T的位置+1,因为LCA在向上走时考虑过了,所以向下走时,
在LCA的位置-1,对于每一个观察员,在以深度tim[i]-dep[i]为根的线段树中查询他所能控制到的左右区间的sum即可
ATTENTION!!!
有可能dep[S]-2*dep[LCA(S,T)]或tim[i]-dep[i]变成一个负数,所以我们在插入或查询过程中都加上一个较大的正数即可
向上走和向下走 中间 线段树一定要清空!!!
我们再谈一谈线段树的动态开点,由于此题很骚,每个深度都要建一棵线段树,如果像普通的线段树一样,
每个深度都建一棵1-n的线段树,我可以很负责任的告诉你 不MLE才怪!!!
所以我们要动态开点,(普通线段树中