【学习笔记】树链剖分系列二——LCA

没想到吧,今天还有时间,我还能更!

其实是打摆多了连自己都觉得不合适了,又不想做题,只能水水博客了。

书接上回:【学习笔记】树链剖分系列1——树链剖分(重链剖分)——我是不是应该写一篇树链剖分呢

上回中,我埋了个伏笔:

是不是有点像倍增LCA。

并且我在下方给出了LCA 的题:

求LCA
P4281 [AHOI2008]紧急集合 / 聚会
P1967 [NOIP2013 提高组] 货车运输
P4427 [BJOI2018]求和

所以,回收伏笔,来看看树剖怎么求LCA。

前置知识:

还是那一大坨定义:

重儿子:对于每一个非叶子节点,它的儿子中,子树最大的儿子,为该节点的重儿子。

轻儿子:对于每一个非叶子节点,它的儿子中,除重儿子外的所有儿子即为轻儿子。

重边:连接任意两个重儿子的边叫做重边。

轻边:除重边以外的所有边。

重链:相邻重边连起来的,一条连接重儿子的链叫重链。

轻链:相邻轻边连起来的,一条连接轻儿子的链叫轻链。

思想

树剖的核心在于跳链,这和倍增不谋而合,所以,LCA的树剖求法类似倍增。

先进行两边 \(dfs\),维护出那些数组。

设两个节点,\(x\)\(y\)

每次让 \(deep[top[x]]\)\(deep[top[y]]\) 大的在下边,然后让它往上跳。

跳到最后,\(x\)\(y\) 肯定在一条重链上了,这时候深度小的节点就是节点 \(x\)\(y\) 的LCA。

int Get_Lca(int x, int y){
    while(top[x] != top[y]){
        if(deep[top[x]] < deep[top[y]]) swap(x, y);
        x = fa[top[x]];
    }

    if(deep[x] > deep[y]) swap(x, y);

    return x;
}

就这么简单。其实跟下一篇还有点联系来着。

我们接下来来看题:

P4281 [AHOI2008]紧急集合 / 聚会

给出三个点,找出一个到三个点花费的总和最小的点,以及最小的花费。

让集合点最优,一定在这三个点相互通达的路径上。多画画图,感觉就出来了(我博客里懒得画了)。

然后,会发现,三个点如果两两求LCA,会有两个LCA相同,这时的最优集合点一定是那个不同的LCA。

花费可以树上差分,就是 \(deep[x] + deep[y] + deep[z] - deep[lca1] - deep[lca2] - deep[lca3]\)

Code

#include<cstdio>
#include<algorithm>

using namespace std;

const int MAXN = 5e5 + 10;
int n, m, cnt, num;
int head[MAXN];
int fa[MAXN], son[MAXN], deep[MAXN], size[MAXN];
int dfn[MAXN], top[MAXN];

struct Edge{
    int to, next;
}e[MAXN << 1];

inline void Add(int u, int v){
    e[++cnt].to = v;
    e[cnt].next = head[u];
    head[u] = cnt;
}

void dfs_deep(int rt, int father, int depth){
    size[rt] = 1;
    fa[rt] = father;
    deep[rt] = depth;
    
    int max_son = -1;
    for(register int i = head[rt]; i; i = e[i].next){
        int v = e[i].to;
        if(v == father) continue;

        dfs_deep(v, rt, depth + 1);
        size[rt] += size[v];

        if(size[v] > max_son){
            son[rt] = v;
            max_son = size[v];
        }
    }
}

void dfs_top(int rt, int top_fa){
    dfn[rt] = ++num;
    top[rt] = top_fa;

    if(!son[rt]) return;
    dfs_top(son[rt], top_fa);

    for(register int i = head[rt]; i; i = e[i].next){
        int v = e[i].to;
        if(!dfn[v]) dfs_top(v, v);
    }
}

int Get_Lca(int x, int y){
    while(top[x] != top[y]){
        if(deep[top[x]] < deep[top[y]]) swap(x, y);
        x = fa[top[x]];
    }

    if(deep[x] > deep[y]) swap(x, y);

    return x;
}

inline int read(){
    int x = 0, f = 1;
    char c = getchar();

    while(c < '0' || c > '9'){
        if(c == '-') f = -1;
        c = getchar();
    }
    while(c >= '0' && c <= '9'){
        x = (x << 1) + (x << 3) + (c ^ 48);
        c = getchar();
    }

    return x * f;
}

int main(){
    n = read(), m = read();
    for(register int i = 1; i <= n - 1; i++){
        int u, v;
        u = read(), v = read();
        Add(u, v);
        Add(v, u);
    }

    dfs_deep(1, 0, 1);
    dfs_top(1, 1);

    for(register int i = 1; i <= m; i++){
        int x, y, z;
        x = read(), y = read(), z = read();

        int anc_final;
        int anc1 = Get_Lca(x, y);
        int anc2 = Get_Lca(x, z);
        int anc3 = Get_Lca(y, z);
        int dis = deep[x] + deep[y] + deep[z] - deep[anc1] - deep[anc2] - deep[anc3];

        if(anc1 == anc2) anc_final = anc3;
        else if(anc1 == anc3) anc_final = anc2;
        else if(anc2 == anc3) anc_final = anc1;
        
        printf("%d %d\n", anc_final, dis);
    }

    return 0;
}

P4427 [BJOI2018]求和

这一眼直接LCA + 树上差分。

可以观察到,\(k <= 50\)\(n <= 3e5\),并不大。
一棵树的平均深度是 \(\log n\),退化成链的最坏深度是 \(n\),所以我们可以预处理出 \(1\)~\(n\)\(1\)~\(k\) 次幂,同时维护一个 \(1\) ~ \(k\) 次幂的树上前缀和。

最后的答案就是

\[sum[u][k] + sum[v][k] - 2 * sum[lca][k] + val[lca][k] \]

记得取模后被减数可能小于减数,所以减完要先加上模数再取模,考试的时候因为这个 100 pts →
0 pts。

Code

#include<cstdio>
#include<algorithm>

#define LL long long

using namespace std;

const int MAXN = 3e5 + 10, MAXK = 55;
const int Mod = 998244353;
int n, m, cnt, num;
int head[MAXN];
int fa[MAXN], son[MAXN], size[MAXN], deep[MAXN];
int dfn[MAXN], top[MAXN];
LL p[MAXN][MAXK], sum[MAXN][MAXK];

struct Edge{
   int to, next;
}e[MAXN << 1];

inline void Add(int u, int v){
   e[++cnt].to = v;
   e[cnt].next = head[u];
   head[u] = cnt;
}

void dfs_deep(int rt, int father, int depth){
   size[rt] = 1;
   fa[rt] = father;
   deep[rt] = depth;
   
   for(register int i = 1; i <= 50; i++)
       sum[rt][i] = 1LL * (sum[fa[rt]][i] + p[deep[rt]][i]) % Mod;

   int max_son = -1;
   for(register int i = head[rt]; i; i = e[i].next){
       int v = e[i].to;
       if(v == father) continue;

       dfs_deep(v, rt, depth + 1);
       size[rt] += size[v];

       if(max_son < size[v]){
           son[rt] = v;
           max_son = size[v];
       }
   }
}

void dfs_top(int rt, int top_fa){
   dfn[rt] = ++num;
   top[rt] = top_fa;

   if(!son[rt]) return;
   dfs_top(son[rt], top_fa);

   for(register int i = head[rt]; i; i = e[i].next){
       int v = e[i].to;
       if(!dfn[v]) dfs_top(v, v);
   }
}

int Get_Lca(int x, int y){
   while(top[x] != top[y]){
       if(deep[top[x]] < deep[top[y]]) swap(x, y);
       x = fa[top[x]];
   }

   if(deep[x] > deep[y]) swap(x, y);

   return x;
}

LL Get_Sum(int x, int y, int k){
   int lca = Get_Lca(x, y);
   return (1LL * (sum[x][k] + sum[y][k]) % Mod - (2 * sum[lca][k] % Mod) + Mod + p[deep[lca]][k]) % Mod;
}

void init(){
   for(register int i = 1; i <= n; i++){
       p[i][0] = 1;
       for(register int j = 1; j <= 50; j++)
           p[i][j] = 1LL * p[i][j - 1] * i % Mod;
   }
}

inline int read(){
   int x = 0, f = 1;
   char c = getchar();

   while(c < '0' || c > '9'){
       if(c == '-') f = -1;
       c = getchar();
   }
   while(c >= '0' && c <= '9'){
       x = (x << 1) + (x << 3) + (c ^ 48);
       c = getchar();
   }

   return x * f;
} 

int main(){
   n = read();
   init();

   for(register int i = 1; i <= n - 1; i++){
       int u, v;
       u = read(), v = read();
       Add(u, v);
       Add(v, u);
   }

   dfs_deep(1, 0, 0);
   dfs_top(1, 1);

   m = read();
   for(register int i = 1; i <= m; i++){
       int x, y, k;
       x = read(), y = read(), k = read();
       printf("%lld\n", 1LL * Get_Sum(x, y, k));
   }

   return 0;
}
posted @ 2022-08-17 21:35  TSTYFST  阅读(67)  评论(0编辑  收藏  举报