应该算是比较基础的知识了吧 …… 随便写写,主要内容是证明。

例题(现编的):有一棵 \(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)\) 。实际操作中可能空间有两倍的常数。

哇怎么这么短就写完了 ……

12-28 13:24