您的位置:

长链剖分

一、简介

长链剖分是一种基于动态规划思想的算法,用于解决静态区间查询的问题。它将一条长链分为若干个短链,使得查询的时间复杂度降至 O(log n),解决了静态区间查询的效率问题。

长链剖分可以被用于很多静态区间查询的场景,比如线段树、树链剖分、主席树等。

二、核心思想

长链剖分的核心思想是将一条较长的链分为若干个较短的链,使得查询的时间复杂度降低到 O(log n)。

具体实现过程中,我们可以将一条长链分割成 k 条较短的链,每条链的长度为 O(log n),并建立一个虚树,虚树是由长链上的一些重链、轻链以及一些根据 LCA(最近公共祖先)得到的节点组成的一棵树。

每个节点都对应着一条较短的链,较短链上的信息可以预处理,查询的时候只需要暴力查询所有较短链上的信息即可。

//代码示例
void dfs(int u, int fa, int &k) {
    size[u] = 1;
    for(int i = head[u]; ~i; i = e[i].nxt) {
        int v = e[i].to;
        if(v == fa) continue;
        dfs(v, u, k);
        size[u] += size[v];
        if(size[v] > size[son[u]]) son[u] = v;
    }
    if(size[u] > size[son[u]] + 1) //重链
        c[++k] = u;
}

三、详细实现

1、建虚树

在实现长链剖分之前,需要先建立虚树。虚树是一棵由长链上的一些重链、轻链以及一些根据 LCA(最近公共祖先)得到的节点组成的一棵树。

具体操作如下:

1)将整个链缩成点。将一条长链缩成一个点作为虚树的根节点,则原先的长链上面所有的点成为虚树上的叶子节点。

2)可以根据 LCA 算法,将每一个询问中的区间变成一颗虚子树,其中 LCA 到询问两端的点的路径就是虚子树上的重链,而非这些点到 LCA 的路径就是轻链。

//代码示例
void dfs(int u, int fa, int &k) {
    size[u] = 1;
    for(int i = head[u]; ~i; i = e[i].nxt) {
        int v = e[i].to;
        if(v == fa) continue;
        dfs(v, u, k);
        size[u] += size[v];
        if(size[v] > size[son[u]]) son[u] = v;
   }
    if(size[u] > size[son[u]] + 1) //重链
        c[++k] = u;
}

void dfs2(int u, int fa, int top) {
    id[u] = ++cnt;
    tp[u] = top;
    rk[cnt] = u;
    if(son[u]) dfs2(son[u], u, top);
    for(int i = head[u]; ~i; i = e[i].nxt)
        if(e[i].to != fa && e[i].to != son[u]) dfs2(e[i].to, u, e[i].to);
}

void build(int L, int R, int rt) {
    if(L == R) {
        tr[rt].push_back(p[rk[L]]);
        return;
    }
    int mid = (L + R) >> 1;
    build(L, mid, ls(rt)); build(mid + 1, R, rs(rt));
    merge(tr[ls(rt)].begin(), tr[ls(rt)].end(), tr[rs(rt)].begin(), tr[rs(rt)].end(), back_inserter(tr[rt]));
}

2、长链剖分

有了虚树之后,就可以进行长链剖分了。可以按照以下步骤进行:

1)根据每个节点的子树大小,判断重儿子重链,轻儿子轻链。

2)每次查询的时候从查询区间两个端点向上走,直到走到同一条链上,查询沿途链上的信息。

代码实现如下:

//代码示例
struct node {
    int l, r;
    LL sum;
    node() {}
    node(int l, int r, LL sum): l(l), r(r), sum(sum) {}
};

vectorT[maxn << 2];

void query(int x, int y, int Lim, int u, int fa, vector
    &v) {
    if(x > y || x > Lim) return;
    if(y <= Lim) {
        auto it1 = lower_bound(T[u].begin(), T[u].end(), node(x, -1, -1));
        auto it2 = upper_bound(T[u].begin(), T[u].end(), node(y, -1, -1));
        for(auto it = it1; it != it2; it++) v.push_back(*it);
        return;
    }
    if(tp[x] == tp[y]) {
        if(id[x] > id[y]) swap(x, y);
        auto it1 = lower_bound(T[u].begin(), T[u].end(), node(id[x], -1, -1));
        auto it2 = upper_bound(T[u].begin(), T[u].end(), node(id[y], -1, -1));
        for(auto it = it1; it != it2; it++) v.push_back(*it);
        return;
    }
    if(dep[tp[x]] < dep[tp[y]]) swap(x, y);
    auto it1 = lower_bound(T[u].begin(), T[u].end(), node(id[tp[x]], -1, -1));
    auto it2 = upper_bound(T[u].begin(), T[u].end(), node(id[x], -1, -1));
    for(auto it = it1; it != it2; it++) v.push_back(*it);
    query(fa, fa, Lim, u, fa, v);
}

   
  

四、优化

在实际应用过程中,有若干优化方法可以减小长链剖分的常数和复杂度,包括但不限于:

1)将长链剖分和主席树结合使用,可以进一步优化空间;

2)对于一些复杂的边权计算场景,可以先对边权进行预处理,以减小边权计算的复杂度;

3)使用同余定理等其他技巧来加速。

五、总结

长链剖分是一种重要的静态区间查询算法,可以有效地提高查询效率。通过对长链剖分的实现及优化,我们可以更加高效地解决一些静态区间查询的问题。