应该算是比较基础的知识了吧 …… 随便写写,主要内容是证明。
例题(现编的):有一棵 (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(nlog N)) 。而访问到一个结点才会新开一个结点,所以空间复杂度也是 (O(nlog N)) 。实际操作中可能空间有两倍的常数。
哇怎么这么短就写完了 ……