O(n2)
做法
枚举每个起点,从该点开始
dfs
,把它到终点的路径上能观察到它的观察员数组下标
+1
。
树的形态为链
枚举每个观察员,统计左右两边与它相距为
Wj
的节点。
时间复杂度
O(n)
。
所有的
Si=1
以
1
为根,枚举每个观察员,发现只有它的
Wj=deep[j]
时才会观察到玩家(设
deep[1]=0
),如果它的
Wj=deep[j]
,就统计它的子树上有多少个节点会有玩家到达。预处理
f[i]
表示子树
i
的和即可。
时间复杂度
O(n)
。
所有的
Ti=1
算法1
以
1
为根,对于每个观察员,只需统计有多少个玩家,起点的深度为
deep[j]+Wj
,并且在它的子树中。
设
f[i]
表示深度为
i
的节点上的玩家数量,但如何保证在观察员的子树中这一条件?
线段树合并可以搞,叶子节点的下标
i
为
1⋯deepmax
,记录的值为起点的深度为
i
的玩家的数量,从原树的底部向上依次合并。
时间复杂度
O(nlogn)
。
算法2
发现其实好像不用那么麻烦,下面有一个很很很巧妙的做法——
设
f[i]
表示起点的深度为
i
的玩家有多少个,初始时为空,从
1
号节点开始
dfs
,每访问到一个点
j
,就
++f[deep[j]]
,访问到节点
j
时,记录一下
f[deep[j]+Wj]
的大小,然后继续往下递归它的子树,递归回来后,再查看一下
f[deep[j]+Wj]
的大小,看比原来多了多少,这个差值就是答案。
时间复杂度
O(n)
。
满分做法
上面说了起点位于当前子树内的做法,下面再考虑终点位于当前子树内的?
算法1
还是用线段树合并。比上面多维护一个如下所述的
g
数组即可。
算法2
继续用
dfs
计算差值,外加一个容斥。
用
f
数组计算起点位于当前子树内的玩家个数,方法如
Ti=1
的算法2所述;
用
g
数组计算终点位于当前子树内的玩家个数:
设某个玩家的终点为
i
且在子树
j
中,起点到终点的长度为
Li
,则当
Li−(deep[i]−deep[j])=Wj
时,就要被统计到观察员
j
的观察数目中。这个式子可以转化成:
Li−deep[i]=Wj−deep[j]
由于
Li
可能小于
deep[i]
,于是我们将等式两边同时加
n
。
任选一个根节点开始
dfs
,每访问到一个终点
i
,就
++g[Li−deep[i]+n]
,同时记录一下当前的
g[Wj−deep[j]+n]
,然后继续递归子树,递归回来后,计算一下
g[Wj−deep[j]+n]
多了多少,统计答案。
容易发现,如果起点和终点都在子树
j
中,会被误计入
j
的答案,最后再减掉即可。
我们在倍增计算
Li
时,就可以同时在
fa[lca]
处
+1
,表示有一个玩家的起点与终点都在
fa[lca]
的子树内。