筆者最近意外的發現 筆者的我的網站 http://tiankonguse.com/ 的不少文章被其它網站轉載,可是轉載時未聲明文章來源或參考自 http://tiankonguse.com/ 網站,所以,筆者添加此條聲明。php
鄭重聲明:這篇記錄《 【百度之星2014~複賽)解題報告】The Query on the Tree》轉載自 http://tiankonguse.com/ 的這條記錄:http://tiankonguse.com/record/record.php?id=673git
這幾天把畢業答辯的事弄完了,因而買票出來玩,結果週六是百度之星的複賽,因而我就沒有辦法來作比賽了,不過看了看題,目測能夠過我兩三道題.github
今天已是比賽的次日了,我還一直沒有時間來A掉這些題,今晚抽空先把最簡單的線段樹那道題A了再說.算法
題目說的很清楚了,本身看吧.優化
有一棵樹,樹的每一個點有點權,每次有三種操做: 1. Query x 表示查詢以x爲根的子樹的權值和。 2. Change x y 表示把x點的權值改成y(0<=y<=100)。 3. Root x 表示把x變爲根。
這道題的數據起始很弱的.網站
我最初的想法就能夠把這道題過掉.spa
首先對這個樹按1爲根dfs根優先編號,這個應該沒有什麼疑問..net
編號的好處是一個子樹變爲了一個連續的區間.blog
編號的時候保存一下這個子樹的編號區間,保存在子樹的根上.get
編號的時候順便計算一會兒樹的權值和.
編號的時候記錄一下一個節點的父節點.
修改操做
先說說修改操做,修改某個節點時,就算出這個節點應該增長多少,而後從這個節點開始更新,一直更新到根1.
平均複雜度 O( log(n) )
最壞複雜度 O( n )
設置根
這裏咱們須要一個變量來表示目前的根是那個節點,好比使用root變量,默認值是1.
設置根只須要把根變量更新一下便可.
平均複雜度 O( 1 )
最壞複雜度 O( 1 )
查詢操做
查詢的時候分三種狀況:
1.查詢的節點是目前的根
這個時候答案顯然是整個樹的權值和,返回 根1的權值和便可.
2.目前的根不是查詢的節點的某個子孫(即根不在查詢的子樹裏面)
這個時候,答案和根是1的狀況相同,及直接返回查詢節點的權值和便可.
怎麼判斷根是否是查詢節點的子孫呢?
日常的方法是用 LCA 查詢,這裏我直接使用子樹區間來判斷便可.
3.目前的根是查詢節點的某個子孫.
這個時候,咱們想象一下,咱們拿起根,查詢節點的子孫有那些呢?
即那些會在查詢節點的下面呢?
假設查詢節點是 x, x的一個兒子是y, 根是y的一個子孫(也多是y).
這個時候,咱們拿起根,x 應該變成 y 的兒子了吧.
這時樹的權值應該是 x 原先的權值和 - y 節點的權值和 + 不在x子樹區間的全職和.
而後,咱們能夠發現 x 原先的權值和 + 不在x子樹區間的權值和 = 整個樹的權值和.
故最終答案是 整個樹的權值和 - y節點的權值和.
問題:怎麼找到y節點.
有兩個方法:
1.枚舉x的兒子來判斷
2.從根不斷的找父親來判斷.
因爲題意沒有說最多兒子有多少個,因此第一個方法最壞狀況下爲 O( n ) (不少兒子)
對應的,第二個方法最壞狀況下也是 O( n ) (樹退化爲鏈表).
不過咱們不用管最壞狀況,先這樣實現了再說.
綜合操做複雜度:log(n)
首先對於修改操做,線段樹優化後可使最壞狀況達到 O( log( n ) ).
對於查詢操做,因爲須要知道 x 的那個兒子 y, 這個我目前沒有想到 O( log( n ) ) 的方法.
學弟說那隻能使用二分了.
可是怎麼二分呢?
發現二分不了,不過可使用隨機算法來優化找兒子的效率.
起初咱們是遍歷x的全部兒子,這裏咱們隨機挑一個兒子來尋找.這也算是一個比較好的優化方法吧.
暴力版代碼 https://github.com/tiankonguse/ACM/blob/master/astar/2014/3/2.2.cpp(比較簡潔)
線段樹優化版代碼 https://github.com/tiankonguse/ACM/blob/master/astar/2014/3/2.cpp
對於上面說的幾個方法我只實現了兩個,其餘的都很簡單,有興趣的朋友能夠嘗試一下.
http://blog.csdn.net/hongrock/article/details/27839237(這個參考主要用於確認暴力不會超時,若是精心構造數據,這個方法會超時的)