树链剖分浅析——(板子+[NOI2015]软件包管理器)

一.先知其用

树链剖分有两个用途:
①求树上节点x到节点y的路径上的点权和
②改变树上节点x到节点y的路径上所有点的点权
但是,这两个用途不都可以用LCA实现吗?
是的,但是我们要求的是在线的修改和查询,LCA这种东西除非你敢修改一次就重新建一次LCA,这时间一下子就飙上去了。
所以,这正是我们学树链剖分的意图。

二.预备知识

1.预备概念

重儿子:父亲结点的所有儿子结点中,子树拥有结点数最多的结点。
轻儿子:父亲结点除了重儿子以外的所有儿子。
重边:父亲结点与重儿子的连边。
轻边:父亲结点与轻儿子的连边。
重链:所有重边所组成的一条链。

2.变量声明

f[u]:保存点u的父亲结点
d[u]:保存点u的深度
size[u]:保存以u为根节点的子树的结点个数
son[u]:保存点u的重儿子
top[u]:保存点u所在重链的顶端结点
id[u]:保存点u进行重新编号后的新的编号

三.操作过程

这个其实不太难,但一定要把上面的概念理解清楚。

1.预处理

既然我们定义了这么多变量,定然是要一个一个处理出来的撒。

1.1.dfs1

首先,大家看,第一遍DFS能够处理出来那些数据:
定然是:f[u], d[u], size[u], son[u]
Code:

void dfs1 (int x, int fa, int dep){//x是指此节点,fa是指父节点,dep是指当前深度
    d[x] = dep;
    siz[x] = 1;
    f[x] = fa;
    int maxs = -1;
    for (int i = 0; i < G[x].size (); i ++){
        int tmp = G[x][i];
        if (tmp != fa){
            dfs1 (tmp, x, dep + 1);
            if (siz[tmp] > maxs)//建议大家像这样写,其他写法可能有bug
                son[x] = tmp, maxs = siz[tmp];
            siz[x] += siz[tmp];
        }
    }
}

1.2.dfs2

然后,看懂了第一个深搜,就来处理top[u], id[u]
注意,我们处理出来之后,同一条重链上的点的id是从上到下递增的。
这就有点考技术,大家想一想,详见代码。
Code:

void dfs2 (int x, int fa){//我这里id是用dfn代替的,这个fa其实并不代表父亲节点,而是代表重链的顶点
    cnt ++;
    top[x] = fa;
    dfn[x] = cnt;
    w[cnt] = val[x];
    if (! son[x])//如果重链到头了,就返回
        return ;
    dfs2 (son[x], fa);
    for (int i = 0; i < G[x].size (); i ++){
        int tmp = G[x][i];
        if (tmp != f[x] && tmp != son[x]){//枚举下一条重链的开端
            dfs2 (tmp, tmp);
        }
    }
}

2.开始操作

重头戏来了,现在,我们正式开始操作。

2.1.操作1:求节点x到节点y的路径上所有点权的总和

这里我们就要结合线段树了。
我们建一棵线段树出来(注意,按每个点的新编号进行建树
然后,选节点x和节点y中深度较深的一个点出来进行爬树操作(爬到它那条重链的顶点的父亲节点
然后一边爬一边通过线段树求和就行了。
爬到什么时候为止呢?
自然是两个节点已经到了同一条重链的时候结束。
最后再加上爬完树之后两点之间的点权和就行了。
其实挺好理解的。
Code

扫描二维码关注公众号,回复: 8762005 查看本文章
int get_sum (int x, int y){
    int Sum = 0;
    while (top[x] != top[y]){
        if (d[top[x]] < d[top[y]])//让节点x成为深度最深的节点
            swap (x, y);
        Sum = (Sum + Query (1, dfn[top[x]], dfn[x])) % p;//就和
        x = f[top[x]];//爬到重链顶尖的父节点
    }
    if (dfn[x] > dfn[y])//最后再加上爬完树之后两点之间的点权和
        Sum = (Sum + Query (1, dfn[y], dfn[x])) % p;
    else
        Sum = (Sum + Query (1, dfn[x], dfn[y])) % p;
    return Sum;
}

2.2.操作2:修改节点x到节点y的路径上所有点权

显而易见,就是把求和改成了修改,将上面的代码改一下就行了。
Code:

int Add (int x, int y, int z){
    int Sum = 0;
    while (top[x] != top[y]){
        if (d[top[x]] < d[top[y]])
            swap (x, y);
        Update (1, dfn[top[x]], dfn[x], z);
        x = f[top[x]];
    }
    if (d[x] > d[y])
        swap (x, y);
    Update (1, dfn[x], dfn[y], z);
}

3.线段树温馨提醒

如果线段树给打错了,肯定是十分恼火的。因为有时候这本是一道树链剖分的题,被你给做成了一道线段树的黑题。
错误一般都集中在建树和下传懒标记上,下面给出代码:

//建树
void build (int l, int r, int Index){
    a[Index].L = l;
    a[Index].R = r;
    a[Index].lazy = a[Index].s = 0;
    if (l != r){
        int mid = (l + r) / 2;
        build (l, mid, Index * 2);
        build (mid + 1, r, Index * 2 + 1);
        a[Index].s = (a[Index * 2].s + a[Index * 2 + 1].s) % p;
    }
    else
        a[Index].s = w[l] % p;//注意
}
//下传懒标记
void push_down (int Index){
    if (a[Index].L != a[Index].R){
        a[Index * 2].lazy = (a[Index * 2].lazy + a[Index].lazy) % p;
        a[Index * 2 + 1].lazy = (a[Index * 2 + 1].lazy + a[Index].lazy) % p;
        a[Index * 2].s = (a[Index * 2].s + (a[Index * 2].R - a[Index * 2].L + 1) * a[Index].lazy % p) % p;
        a[Index * 2 + 1].s = (a[Index * 2 + 1].s + (a[Index * 2 + 1].R - a[Index * 2 + 1].L + 1) * a[Index].lazy % p) % p;
    }
    a[Index].lazy = 0;
}

四.实战演练

1.洛谷板题

1.1.题目

传送门

1.2.题解

会板子就会做

1.3.Code

#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
#include <algorithm>
#include <cstdlib>
using namespace std;

#define M 100005

struct node {
    int L, R, lazy, s;
}a[M * 4];
vector <int> G[M];
int n, m, r, p, val[M], siz[M], son[M], cnt, dfn[M], w[M], top[M], d[M], f[M];

void build (int l, int r, int Index){
    a[Index].L = l;
    a[Index].R = r;
    a[Index].lazy = a[Index].s = 0;
    if (l != r){
        int mid = (l + r) / 2;
        build (l, mid, Index * 2);
        build (mid + 1, r, Index * 2 + 1);
        a[Index].s = (a[Index * 2].s + a[Index * 2 + 1].s) % p;
    }
    else
        a[Index].s = w[l] % p;
}
void push_down (int Index){
    if (a[Index].L != a[Index].R){
        a[Index * 2].lazy = (a[Index * 2].lazy + a[Index].lazy) % p;
        a[Index * 2 + 1].lazy = (a[Index * 2 + 1].lazy + a[Index].lazy) % p;
        a[Index * 2].s = (a[Index * 2].s + (a[Index * 2].R - a[Index * 2].L + 1) * a[Index].lazy % p) % p;
        a[Index * 2 + 1].s = (a[Index * 2 + 1].s + (a[Index * 2 + 1].R - a[Index * 2 + 1].L + 1) * a[Index].lazy % p) % p;
    }
    a[Index].lazy = 0;
}
void Update (int Index, int l, int r, int v){
    if (a[Index].L > r || a[Index].R < l)
        return ;
    push_down (Index);
    if (l <= a[Index].L && a[Index].R <= r){
        a[Index].lazy = v;
        a[Index].s = (a[Index].s + (a[Index].R - a[Index].L + 1) * v % p) % p;
        return ;
    }
    Update (Index * 2, l, r, v);
    Update (Index * 2 + 1, l, r, v);
    a[Index].s = (a[Index * 2].s + a[Index * 2 + 1].s) % p;
}
int Query (int Index, int l, int r){
    if (a[Index].L > r || a[Index].R < l)
        return 0;
    push_down (Index);
    if (l <= a[Index].L && a[Index].R <= r)
        return a[Index].s;
    return (Query (Index * 2, l, r) + Query (Index * 2 + 1, l, r)) % p;
}
void dfs1 (int x, int fa, int dep){
    d[x] = dep;
    siz[x] = 1;
    f[x] = fa;
    int maxs = -1;
    for (int i = 0; i < G[x].size (); i ++){
        int tmp = G[x][i];
        if (tmp != fa){
            dfs1 (tmp, x, dep + 1);
            if (siz[tmp] > maxs)
                son[x] = tmp, maxs = siz[tmp];
            siz[x] += siz[tmp];
        }
    }
}
void dfs2 (int x, int fa){
    cnt ++;
    top[x] = fa;
    dfn[x] = cnt;
    w[cnt] = val[x];
    if (! son[x])
        return ;
    dfs2 (son[x], fa);
    for (int i = 0; i < G[x].size (); i ++){
        int tmp = G[x][i];
        if (tmp != f[x] && tmp != son[x]){
            dfs2 (tmp, tmp);
        }
    }
}
int get_sum (int x, int y){
    int Sum = 0;
    while (top[x] != top[y]){
        if (d[top[x]] < d[top[y]])
            swap (x, y);
        Sum = (Sum + Query (1, dfn[top[x]], dfn[x])) % p;
        x = f[top[x]];
    }
    if (dfn[x] > dfn[y])
        Sum = (Sum + Query (1, dfn[y], dfn[x])) % p;
    else
        Sum = (Sum + Query (1, dfn[x], dfn[y])) % p;
    return Sum;
}
int Add (int x, int y, int z){
    int Sum = 0;
    while (top[x] != top[y]){
        if (d[top[x]] < d[top[y]])
            swap (x, y);
        Update (1, dfn[top[x]], dfn[x], z);
        x = f[top[x]];
    }
    if (d[x] > d[y])
        swap (x, y);
    Update (1, dfn[x], dfn[y], z);
}
int main (){
    scanf ("%d %d %d %d", &n, &m, &r, &p);
    for (int i = 1; i <= n; i ++)
        scanf ("%d", &val[i]);
    for (int i = 1; i < n; i ++){
        int u, v;
        scanf ("%d %d", &u, &v);
        G[u].push_back (v);
        G[v].push_back (u);
    }
    dfs1 (r, 0, 1);
    dfs2 (r, r);
    build (1, n, 1);
    while (m --){
        int f, x, y, z;
        scanf ("%d", &f);
        if (f == 1){
            scanf ("%d %d %d", &x, &y, &z);
            z %= p;
            Add (x, y, z);
        }
        if (f == 2){
            scanf ("%d %d", &x, &y);
            printf ("%d\n", get_sum (x, y));
        }
        if (f == 3){
            scanf ("%d %d", &x, &z);
            z %= p;
            Update (1, dfn[x], dfn[x] + siz[x] - 1, z);
        }
        if (f == 4){
            scanf ("%d", &x);
            printf ("%d\n", Query (1, dfn[x], dfn[x] + siz[x] - 1));
        }
    }
    return 0;
}

2.[NOI2015]软件包管理器

2.1.题目:传送门

2.2.题解

这道题终究是个板题。你就把0和1看成每个点的点权,修改和求和都不变。
但是有个优化,就是求和的时候不必用板子的方法,直接用线段树进行修改,把修改后的根节点的值和原根节点的值做差就是改变的点权和。

2.3.Code

#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
#include <algorithm>
#include <cstdlib>
using namespace std;

#define M 100005

struct node {
    int L, R, lazy, s;
}a[M * 4];
vector <int> G[M];
int n, val[M], siz[M], son[M], cnt, dfn[M], w[M], top[M], d[M], f[M], q;

void build (int l, int r, int Index){
    a[Index].L = l;
    a[Index].R = r;
    a[Index].lazy = 0;
    a[Index].s = r - l + 1;
    if (l != r){
        int mid = (l + r) / 2;
        build (l, mid, Index * 2);
        build (mid + 1, r, Index * 2 + 1);
    }
}
void push_down (int Index){
    if (a[Index].L != a[Index].R){
        if (a[Index].lazy == -1){
            a[Index * 2].lazy = a[Index * 2 + 1].lazy = -1;
            a[Index * 2].s = a[Index * 2 + 1].s = 0;
        }
        if (a[Index].lazy == 1){
            a[Index * 2].lazy = a[Index * 2 + 1].lazy = 1;
            a[Index * 2].s = a[Index * 2].R - a[Index * 2].L + 1;
            a[Index * 2 + 1].s = a[Index * 2 + 1].R - a[Index * 2 + 1].L + 1;
        }
    }
    a[Index].lazy = 0;
}
void Update (int Index, int l, int r, int v){
    if (a[Index].L > r || a[Index].R < l)
        return ;
    push_down (Index);
    if (l <= a[Index].L && a[Index].R <= r){
        a[Index].lazy = v;
        if (v == -1)
            a[Index].s = 0;
        else
            a[Index].s = a[Index].R - a[Index].L + 1;
        return ;
    }
    Update (Index * 2, l, r, v);
    Update (Index * 2 + 1, l, r, v);
    a[Index].s = a[Index * 2].s + a[Index * 2 + 1].s;
}
int Query (int Index, int l, int r){
    if (a[Index].L > r || a[Index].R < l)
        return 0;
    push_down (Index);
    if (l <= a[Index].L && a[Index].R <= r)
        return a[Index].s;
    return Query (Index * 2, l, r) + Query (Index * 2 + 1, l, r);
}
void dfs1 (int x, int fa, int dep){
    d[x] = dep;
    siz[x] = 1;
    f[x] = fa;
    int maxs = -1;
    for (int i = 0; i < G[x].size (); i ++){
        int tmp = G[x][i];
        if (tmp != fa){
            dfs1 (tmp, x, dep + 1);
            if (siz[tmp] > maxs)
                son[x] = tmp, maxs = siz[tmp];
            siz[x] += siz[tmp];
        }
    }
}
void dfs2 (int x, int fa){
    cnt ++;
    top[x] = fa;
    dfn[x] = cnt;
    w[cnt] = val[x];
    if (! son[x])
        return ;
    dfs2 (son[x], fa);
    for (int i = 0; i < G[x].size (); i ++){
        int tmp = G[x][i];
        if (tmp != f[x] && tmp != son[x]){
            dfs2 (tmp, tmp);
        }
    }
}
void Add (int x, int y){
    int Sum = 0;
    while (top[x] != top[y]){
        if (d[top[x]] < d[top[y]])
            swap (x, y);
        Update (1, dfn[top[x]], dfn[x], -1);
        x = f[top[x]];
    }
    if (dfn[x] > dfn[y]){
        Update (1, dfn[y], dfn[x], -1);
    }
    else{
        Update (1, dfn[x], dfn[y], -1);
    }
}
int main (){
    scanf ("%d", &n);
    val[1] = 1;
    for (int i = 1; i < n; i ++){
        int u;
        scanf ("%d", &u);
        G[i + 1].push_back (u + 1);
        G[u + 1].push_back (i + 1);
        val[i + 1] = 1;
    }
    scanf ("%d", &q);
    dfs1 (1, 0, 1);
    dfs2 (1, 1);
    build (1, n + 1, 1);
    while (q --){
        char c[20];
        int u;
        scanf ("%s%d", c, &u);
        u ++;
        if (c[0] == 'i'){
            int t = a[1].s;
            Add (1, u);
            printf ("%d\n", t - a[1].s);//直接将修改前后的根节点做差就是答案了
        }
        else{
            printf ("%d\n", siz[u] - Query (1, dfn[u], dfn[u] + siz[u] - 1));
            Update (1, dfn[u], dfn[u] + siz[u] - 1, 1);
        }
    }
    return 0;
}

谢谢!

发布了61 篇原创文章 · 获赞 32 · 访问量 8338

猜你喜欢

转载自blog.csdn.net/weixin_43908980/article/details/101121862