Luogu P3384 【模板】树链剖分

传送门~

树链剖分,顾名思义,就是把树分成链。

通过这个方法,可以优化对树上两点间路径某一点子树的修改和查询的操作,等。

流程

$dfs1()$

在这个函数中,要处理出每个节点的:

  • 深度dep[]
  • 父亲fa[]
  • 大小siz[]
  • 重儿子编号hson[]

一个节点的siz[],是包括它自己、它的儿子、它儿子的儿子……一共的节点数量。

所谓的重儿子,就是一个节点的儿子中,siz[]最大的那一个。

叶子节点没有儿子,所以也没有重儿子。

这个函数就是普通的遍历整棵树,每到一个点记录dpth[],siz[]初始值为1。

对于所连的每一条边,标记儿子的fa[]并递归$dfs$,回溯时将子树大小加给父亲的siz[],并判断是否更新重儿子hson[]。

void dfs1(int u){
    dpth[u] = dpth[fa[u]]+1;
    siz[u] = 1;
    for(int i = head[u];i;i = nxt[i]){
        int v = to[i];
        if(v == fa[u])continue;
        fa[v] = u;
        dfs1(v);
        siz[u] += siz[v];
        if(siz[v] > siz[hson[u]]) hson[u] = v;
    }
}

知道了重儿子,如果沿着树走一遍,并且每次都先走重儿子,就可以把整个树拆成从大到小的很多链!

$dfs2()$

在这个函数中,要处理出每个节点的:

  • 新编号,也就是先走重儿子的$dfs$序dfn[]
  • 新编号对应的原编号point[]
  • 所在链的顶端的点top[]

每到一个点,首先直接记录它的dfn[]、point[]、top[](有点类似father)。

如果没有重儿子,则是叶子节点,直接$return$;

如果有重儿子,则这条链还没有结束,继续递归,top[]不变;

如果除了重儿子还有其他的儿子,则每个轻儿子都是一条新链,继续递归,且新链的top[]是这个轻儿子。

void dfs2(int u,int t){
    dfn[u] = ++cnt;
    point[cnt] = u;
    top[u] = t;
    if(!hson[u])return;
    dfs2(hson[u],t);
    for(int i = head[u];i;i = nxt[i]){
        int v = to[i];
        if(v == fa[u] || v == hson[u])continue;
        dfs2(v,v);
    }
}

这样,“链”的部分就算是处理好了$-w-$

接下来,根据新的编号dfn[],建一棵线段树。

线段树需要的函数:$build$、$pushdown$、$modify$、$query$

下面来看需要支持的操作:

  1. 将树从x到y结点最短路径上所有节点的值都加上z
  2. 求树从x到y结点最短路径上所有节点的值之和
  3. 将以x为根节点的子树内所有节点值都加上z
  4. 求以x为根节点的子树内所有节点值之和

树上路径

求树上路径,听起来很像$LCA$……实际上,树链剖分的确可以求出$LCA$。

需要再写两个函数:$getmodify$,$getquery$。这里以查询操作为例。

如果x,y的top[]不同,则它们一定不在同一条链上。

和倍增$LCA$的做法相似,每次将top[]较深,即所在链的顶端比较靠下的节点,跳到它的top[]的父亲,这样就上升到了上边的链。

查询这一段路径的值(dfn[top[x]],dfn[x]),并重复操作,直到x,y在同一条链上为止。

同一链上,深度较小的点一定dfn[]较小,也就是在线段树上的编号较小。通过swap,使较浅的作为x,并查询(dfn[x],dfn[y])

int getquery(int x,int y) {
    int ans = 0;
    while(top[x] != top[y]) {
        if(dpth[top[x]] < dpth[top[y]]) swap(x,y);
        ans += query(dfn[top[x]],dfn[x],1,n,1);
        x = fa[top[x]];
    }
    if(dpth[x] > dpth[y]) swap(x,y);
    ans += query(dfn[x],dfn[y],1,n,1);
    return ans;
}

子树

根据dfs序,可以知道,一棵子树内的序号是连续的。

那么,对应到线段树上,即为(dfn[x],dfn[x]+siz[x]-1)。

完整代码如下

#include<iostream>
#include<cstdio>
#include<cstring>
#define MogeKo qwq
using namespace std;
const int maxn = 1e5+10;
int n,m,rt,opt,x,y,z,mod,cnt;
int to[maxn<<1],head[maxn<<1],nxt[maxn<<1];
long long sum[maxn<<2],lazy[maxn<<2];
int dfn[maxn],dpth[maxn],siz[maxn],hson[maxn],fa[maxn],top[maxn],point[maxn];
long long w[maxn];

void add(int x,int y) {
    to[++cnt] = y;
    nxt[cnt] = head[x];
    head[x] = cnt;
}

void dfs1(int u) {
    dpth[u] = dpth[fa[u]]+1;
    siz[u] = 1;
    for(int i = head[u]; i; i = nxt[i]) {
        int v = to[i];
        if(v == fa[u])continue;
        fa[v] = u;
        dfs1(v);
        siz[u] += siz[v];
        if(siz[v] > siz[hson[u]]) hson[u] = v;
    }
}

void dfs2(int u,int t) {
    dfn[u] = ++cnt;
    point[cnt] = u;
    top[u] = t;
    if(!hson[u])return;
    dfs2(hson[u],t);
    for(int i = head[u]; i; i = nxt[i]) {
        int v = to[i];
        if(v == fa[u] || v == hson[u])continue;
        dfs2(v,v);
    }
}

void build(int l,int r,int now) {
    if(l == r) {
        sum[now] = w[point[l]] %mod;
        return;
    }
    int mid = l+r>>1;
    build(l,mid,now<<1),build(mid+1,r,now<<1|1);
    sum[now] = (sum[now<<1] + sum[now<<1|1]) %mod;
}

void pushdown(int l,int r,int now) {
    sum[now] += (r-l+1)*lazy[now]%mod;
    (lazy[now<<1] += lazy[now]) %=mod;
    (lazy[now<<1|1] += lazy[now]) %=mod;
    lazy[now] = 0;
}

void modify(int L,int R,int l,int r,int c,int now) {
    if(L == l && R == r) {
        lazy[now] += c;
        return;
    }
    (sum[now] += (R-L+1)*c ) %=mod;
    int mid = l+r>>1;
    if(R <= mid) modify(L,R,l,mid,c,now<<1);
    else if(L >= mid+1) modify(L,R,mid+1,r,c,now<<1|1);
    else modify(L,mid,l,mid,c,now<<1),modify(mid+1,R,mid+1,r,c,now<<1|1);
}

long long query(int L,int R,int l,int r,int now) {
    if(L == l && R == r) {
        return (sum[now]+lazy[now]*(r-l+1))%mod;
    }
    pushdown(l,r,now);
    int mid = l+r>>1;
    if(R <= mid) return query(L,R,l,mid,now<<1);
    else if(L >= mid+1) return query(L,R,mid+1,r,now<<1|1);
    else return (query(L,mid,l,mid,now<<1) + query(mid+1,R,mid+1,r,now<<1|1)) %mod;
}

void getmodify(int x,int y,int c) {
    while(top[x] != top[y]) {
        if(dpth[top[x]] < dpth[top[y]]) swap(x,y);
        modify(dfn[top[x]],dfn[x],1,n,c,1);
        x = fa[top[x]];
    }
    if(dpth[x] > dpth[y]) swap(x,y);
    modify(dfn[x],dfn[y],1,n,c,1);
}

long long getquery(int x,int y) {
    long long ans = 0;
    while(top[x] != top[y]) {
        if(dpth[top[x]] < dpth[top[y]]) swap(x,y);
        (ans += query(dfn[top[x]],dfn[x],1,n,1)) %=mod;
        x = fa[top[x]];
    }
    if(dpth[x] > dpth[y]) swap(x,y);
    (ans += query(dfn[x],dfn[y],1,n,1)) %=mod;
    return ans;
}

int main() {
    scanf("%d%d%d%d",&n,&m,&rt,&mod);
    for(int i = 1; i <= n; i++)
        scanf("%lld",&w[i]);
    for(int i = 1; i <= n-1; i++) {
        scanf("%d%d",&x,&y);
        add(x,y),add(y,x);
    }
    cnt = 0;
    dfs1(rt),dfs2(rt,rt);
    build(1,n,1);
    for(int i = 1; i <= m; i++) {
        scanf("%d",&opt);
        if(opt == 1) {
            scanf("%d%d%d",&x,&y,&z);
            getmodify(x,y,z%mod);
        }
        if(opt == 2) {
            scanf("%d%d",&x,&y);
            printf("%lld\n",getquery(x,y));
        }
        if(opt == 3) {
            scanf("%d%d",&x,&z);
            modify(dfn[x],dfn[x]+siz[x]-1,1,n,z%mod,1);
        }
        if(opt == 4) {
            scanf("%d",&x);
            printf("%lld\n",query(dfn[x],dfn[x]+siz[x]-1,1,n,1));
        }
    }
    return 0;
}

原文地址:https://www.cnblogs.com/mogeko/p/11229910.html

时间: 2024-08-30 02:39:07

Luogu P3384 【模板】树链剖分的相关文章

[luogu P3384] [模板]树链剖分

[luogu P3384] [模板]树链剖分 题目描述 如题,已知一棵包含N个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作: 操作1: 格式: 1 x y z 表示将树从x到y结点最短路径上所有节点的值都加上z 操作2: 格式: 2 x y 表示求树从x到y结点最短路径上所有节点的值之和 操作3: 格式: 3 x z 表示将以x为根节点的子树内所有节点值都加上z 操作4: 格式: 4 x 表示求以x为根节点的子树内所有节点值之和 输入输出格式 输入格式: 第一行包含4个正整数

【Luogu P3384】树链剖分模板

树链剖分的基本思想是把一棵树剖分成若干条链,再利用线段树等数据结构维护相关数据,可以非常暴力优雅地解决很多问题. 树链剖分中的几个基本概念: 重儿子:对于当前节点的所有儿子中,子树大小最大的一个儿子就是重儿子(子树大小相同的则随意取一个) 轻儿子:不是重儿子就是轻儿子 重边:连接父节点和重儿子的边 轻边:连接父节点和轻儿子的边 重链:相邻重边相连形成的链 值得注意的还有以下几点: 叶子节点没有重儿子也没有轻儿子: 对于每一条重链,其起点必然是轻儿子: 单独一个轻叶子节点也是一条重链: 结合上面三

luoguP3384 [模板]树链剖分

luogu P3384 [模板]树链剖分 题目 #include<iostream> #include<cstdlib> #include<cstdio> #include<cmath> #include<cstring> #include<iomanip> #include<algorithm> #include<ctime> #include<queue> #define rg register

[模板]树链剖分

原题链接:https://www.luogu.org/problemnew/show/P3384 树链剖分+线段树,备用. 等待补充详细解释中. /* 1 x y z x到y最短路径上加上z 2 x y 求x到y的最短路径上的节点值之和 3 x z 以x为根节点的子树内的所有节点值都加上z 4 x 以x为根节点的所有节点值之和 */ #include<cstdio> void read(int &y) { y=0;char x=getchar();int f=1; while(x<

模板 树链剖分BFS版本

//点和线段树都从1开始 //边使用vector vector<int> G[maxn]; int dfs_clock,que[maxn*2],num[maxn],iii[maxn],b[maxn],a[maxn],top[maxn],deep[maxn],fa[maxn],idx[maxn]; //采用静态链表 //a[i] 是初始时树上每个点的权值 //b[i] 是经过bfs后每个点的权值 //idx[i] 是每个点在全局线段树中的下标 void build_List() { int ft

模板——树链剖分

放个板子而已.. #include<stdio.h> #include<algorithm> #define ls root<<1 #define rs root<<1|1 using namespace std; typedef long long ll; int n,m,r;ll res,p; const int MAXN = 100005; ll a[MAXN]; struct edge{ int v,nxt; }e[MAXN<<1]; s

[模板] 树链剖分找LCA

#include <cstdio> #include <cstring> #define MAX 500005 int d[MAX],fa[MAX],size[MAX],top[MAX],son[MAX]; int N,M,S,tot=0; int head[MAX]; struct edge{ int v,next; }G[MAX<<1]; inline void add(int u,int v){ G[++tot].v=v;G[tot].next=head[u];h

树链剖分[模板](洛谷 P3384)

洛谷·[模板]树链剖分 写在前面 首先,在学树链剖分之前最好先把 LCA.树形DP.DFS序 这三个知识点学了 如果这三个知识点没掌握好的话,树链剖分难以理解也是当然的. 树链剖分 树链剖分 就是对一棵树分成几条链,把树形变为线性,减少处理难度 概念 dfs1() dfs2() 对剖过后的树建线段树 处理问题 概念 重儿子:对于每一个非叶子节点,它的儿子中 儿子数量最多的那一个儿子 为该节点的重儿子 轻儿子:对于每一个非叶子节点,它的儿子中 非重儿子 的剩下所有儿子即为轻儿子 叶子节点没有重儿子

Hdu5044Tree 树链剖分

输入输出挂,扩栈挂,模板树链剖分. #include<iostream> #include<cstdio> #include<cstring> #include<map> #include<vector> #include<stdlib.h> #include<algorithm> using namespace std; const int maxn = 111111; struct Node { int next; i