笔者最近意外的发现 笔者的我的网站 http://tiankonguse.com/ 的不少文章被其它网站转载,可是转载时未声明文章来源或参考自 http://tiankonguse.com/ 网站,所以,笔者添加此条声明。php
郑重声明:这篇记录《【百度之星2014~复赛 解题报告~正解】The Query on the Tree》转载自 http://tiankonguse.com/的这条记录:http://tiankonguse.com/record/record.php?id=674git
昨天写了 The Query on the Tree 的解题报告,可是遗留下一个问题,不能算是完美解决这道题.github
由于若是精心构造数据的话,昨天的题解仍是会被卡住的.数组
今天中午睡觉的时候忽然想起一个不会被卡住的方法.优化
可是因为早上玩了一会相似与宠物消消的弱智游戏,因而怎么也停不下来了.网站
一个下午的时光也浪费在了这个弱智游戏上.spa
到了晚上,手机终于没电了,因而来写写这道题的完美解决方法.游戏
这样不管怎么构造数据,tiankonguse都不用担忧程序超时了.get
有一棵树,树的每一个点有点权,每次有三种操做: 1. Query x 表示查询以x为根的子树的权值和。 2. Change x y 表示把x点的权值改成y(0<=y<=100)。 3. Root x 表示把x变为根。 如今度度熊想请更聪明的你帮助解决这个问题。
这篇记录和昨天那一篇紧密相连,建议看看那个记录.it
传送门(http://tiankonguse.com/record/record.php?id=673)
对于这道题,首先须要对树按1为根优先编号.
编号的时候记录子树的权值和以及子树的编号范围.
这样设置根的通常复杂度是O(1), 修改的通常复杂度是O( log( n ) ), 查询的通常复杂度也是 O( log( n ) ).
修改的最坏复杂度是O( n ), 咱们可使用线段树来优化到O( log( n ) ).
对于查询分了三部分,其中有一部分最坏状况下复杂度也是 O( n ).
当时往二分优化上想了,可是目前的信息不知足二分的条件,因此二分不了.
假设目前查询的是x, root 是根, y 是x的某个儿子, root 在 y 的那个子树上.
咱们要搜索y这个节点.
咱们要搜的区间是 left[x]到 left[root], 其中 left[x] 最小,left[y] 不能肯定在那个地方,也不知道 left[y] 的值.
欧拉序列是什么呢?
原来欧拉序列也对树dfs编号了,只不过进入每一个儿子的时候都对当前子树根编号,最后结束时再遍一次号,储存的信息貌似很丰富.
貌似能够.
由于这时x的每一个儿子前面必定有一个编号是x.
而咱们须要的是 root 前面的第一个 x.
又因为 x 是区间内最小值,因此经过二分这个最小值就能够搜到 y 了.
因为须要二分,因此最少是 O( log( n ) ).
每次都须要判断,因此这个咱们须要经过线段树来优化,能够优化到 log( n ).
这样综合复杂度就是 O( log( n ) ^ 2 )
这个固然能够,就是一个二分加线段树.
针对昨天遗留下的问题,这里简单的总结一下解决方法.
遗留的问题是查询的时候,若是root是查询节点x的子孙时,咱们须要找到x的某个儿子y,这个儿子y仍是root的祖先.
这个查找过程用昨天的方法最坏复杂度是O( n ) 的.
这里我找到一个方法:对树dfs编号的时候,每次在儿子前面都添加一个根节点,即把用根节点把各个儿子为根的子树分开.
这样咱们就可使用二分查找x的儿子y了.
由于root前面第一个编号为x的节点和y前面第一个编号为x的节点是相同的.
并且第一个编号为x的节点的下一个节点就是y节点.
因为x仍是整个区间的最小值,因此咱们就能够经过二分区间最小值来找到root前面的第一个编号为x的节点了.
二分优化查询(其余的暴力的代码)https://github.com/tiankonguse/ACM/blob/master/astar/2014/3/2.3.cpp
完整版的代码(两个线段树写为一个了):https://github.com/tiankonguse/ACM/blob/master/astar/2014/3/2.4.cpp
参考
无