應該算是比較基礎的知識了吧 …… 隨便寫寫,主要內容是證明。
例題(現編的):有一棵 \(m\) 個點的有根樹,每個點上有若干個數,\(m\) 個點上共有 \(n\) 個數,數的規模是 \(N\) 。每次詢問給定 \(u,l,r\) ,求 \(u\) 的子樹中有多少個數在 \([l,r]\) 中。
做法是每個點開一棵線段樹,插入這個結點上的數。如果能把一個結點的線段樹的信息在合理的時間內全部插入(或者說「合並」)到它父親的線段樹中,就可以由下往上做一遍樹上遞推,這樣每個結點的線段樹都存儲的是整棵子樹的信息了。
先放代碼吧:
int merge(const int x, const int y)
{
if (!x || !y)
return x + y;
int a = ++cnt;
tree[a].sum = tree[x].sum + tree[y].sum;
tree[a].lt = merge(tree[x].lt, tree[y].lt);
tree[a].rt = merge(tree[x].rt, tree[y].rt);
return a;
}
這個代碼還是相當好理解的,無需贅述。下面來證明一下復雜度。(我想了一中午終於想出一個超簡潔的證明 —— 本來以為會很復雜的)
從代碼中可以看出合並兩棵樹的復雜度約等於這兩棵樹 重合 的結點數。也就是說,每次合並的復雜度不會超過較小的那棵的點數。既然如此,總復雜度就不會超過總點數,也就是 \(O(n\log N)\) 。而訪問到一個結點才會新開一個結點,所以空間復雜度也是 \(O(n\log N)\) 。實際操作中可能空間有兩倍的常數。
哇怎么這么短就寫完了 ……
