模板K级祖先(长链剖分)

Posted ac-evil

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了模板K级祖先(长链剖分)相关的知识,希望对你有一定的参考价值。

题目:lxhgww的奇思妙想——长链剖分

思路

  长链剖分是根据链的长度为准则对一棵树进行剖分,和树剖类似。它有着特殊之处:

  (1)、从一个结点开始往上跳,经过的长链不超过( ext{O}(sqrt n))次。这个就没有树剖优秀了。

  (2)、从一个结点向上跳(k)层,跳到的结点所在长链的长度(>k)。这点很显然,但却很有用。用这一点我们可以( ext{O}(nlog n))预处理,( ext{O}(1))在线查询(K)级祖先。

  假设给定(K),我们求出(t=lfloor log_2 K floor),先利用倍增数组一次跳(t)层,再从该结点向上跳剩下的(K-t)层,我们可以维护每一条长链以及该长链从链顶处维护一个向上拓展的长度为(K)的链,如果跳完这些还在长链上,就直接在长链上跳,否则直接在链顶处拓展的链上往上跳,由于第二点性质,能够保证每次一定不会跳出拓展出来的链。

  是不是非常巧妙!

代码

#include <bits/stdc++.h>

using namespace std;

#define ll long long
#define ull unsigned long long
#define rep(i, a, b) for (int i = a, i##end = b; i <= i##end; ++i)
#define per(i, a, b) for (int i = a, i##end = b; i >= i##end; --i)
#define rep0(i, a) for (int i = 0, i##end = a; i < i##end; ++i)
#define per0(i, a) for (int i = a-1; ~i; --i)
#define chkmax(a, b) a = max(a, b)
#define chkmin(a, b) a = min(a, b)

const int inf = 0x3fffffff;
const int maxn = 311111;

inline int read() {
    int w = 0; char c;
    while (!isdigit(c = getchar())) ;
    while (isdigit(c)) w = (w << 3) + (w << 1) + (c ^ 48), c = getchar();
    return w;
}

int n;

struct Edge {
    int v, nxt;
} e[maxn << 1];
int G[maxn], edges;
void clear() {
    memset(G, -1, sizeof G); edges = 0;
}
void adde(int u, int v) {
    e[edges++] = (Edge){v, G[u]}; G[u] = edges-1;
}

int par[20][maxn], mxd[maxn], son[maxn], dep[maxn], top[maxn]; // 维护2^k的祖先、最深的长链、长脸上的孩子结点、自身深度、链顶。类似于重链剖分
vector<int> up[maxn], chain[maxn]; // 记录长链以及向上跳的链

void dfs1(int u) {
    mxd[u] = dep[u] = dep[par[0][u]] + 1, son[u] = 0;
    for (int i = 1; 1<<i <= n; i++) par[i][u] = par[i-1][par[i-1][u]]; // 倍增
    for (int i = G[u], v; ~i; i = e[i].nxt)
        if ((v = e[i].v) != par[0][u])
            par[0][v] = u, dfs1(v), mxd[v] > mxd[u] && (mxd[u] = mxd[v], son[u] = v); // 与树剖不同的地方在于比较关键字为深度而不是子树大小
}

void dfs2(int u, int tp) {
    top[u] = tp;
    if (son[u]) dfs2(son[u], tp);
    for (int i = G[u], v; ~i; i = e[i].nxt)
        if ((v = e[i].v) != son[u] && v != par[0][u]) dfs2(v, v);
}

int base[maxn]; // 维护log2(n)的下取整

int query(int u, int k) {
    if (!k) return u; // 第一种情况:跳的高度为0。直接返回u
    if (k >= dep[u]) return 0; // 提前判断是否会跳出树,返回0
    u = par[base[k]][u], k ^= 1<<base[k]; // 否则先跳2^base[k],剩余的部分根据长剖的性质直接跳
    return k <= dep[u]-dep[top[u]] ? chain[top[u]][dep[u]-dep[top[u]]-k] : up[top[u]][k-dep[u]+dep[top[u]]]; // 根据跳完是否在这条长链上分类讨论
}

int main() {
    scanf("%d", &n); clear();
    for (int i = 1, u, v; i < n; i++) u = read(), v = read(), adde(u, v), adde(v, u);
    dfs1(1), dfs2(1, 1);
    rep(i, 1, n) if (top[i] == i) { // 处理出来长链和向上跳的k级
        int len = mxd[i] - dep[i]; // 长链的长度为len
        for (int u = i, j = 0; j <= len; u = son[u], j++) chain[i].push_back(u);
        for (int u = i, j = 0; j <= len; u = par[0][u], j++) up[i].push_back(u);
    }
    rep(i, 2, n) base[i] = base[i>>1] + 1;
    for (int ans = 0, Q = read(), u, k; Q; Q--) u = ans ^ read(), k = ans ^ read(), printf("%d
", ans = query(u, k));
    return 0;
}

以上是关于模板K级祖先(长链剖分)的主要内容,如果未能解决你的问题,请参考以下文章

长链剖分总结

长链剖分练习

Vijoslxhgww的奇思妙想

CF860EArkady and a Nobody-men 长链剖分

luogu P5384 [Cnoi2019]雪松果树

bzoj3252攻略(长链剖分+贪心)