树链剖分(轻重链剖分) 讲解 (模板题目 P3384 【模板】轻重链剖分 )

P3384 【模板】轻重链剖分

首先先说一下基本概念:
1.重儿子:一个结点的所有儿子中,大小最大的那个(最重的,所以说只有一个,如果有多个儿子的大小相等那就随便取一个)。
2.轻儿子:一个结点的儿子除了重儿子以外的所有儿子都是轻儿子。(根节点为轻儿子)
3.重链:从一个轻儿子开始,一路往重儿子走,连出来的链叫重链。
4.轻链:除了重链全是轻链。

如下图:
重链即为标染色点连出的边。
dfs序即为即为图中红色标号。
在这里插入图片描述
那么我们如何处理出这个dfs序呢?
通过两个dfs.

第一个dfs需要:
1.标记结点的父亲
2.标记结点的重儿子
3.标记结点的深度
4.结点的大小
第二个dfs需要:
为什么要第二遍才可以(因为需要第一遍跑出来的重儿子)
1.标记重链的开始结点。
2.结点权值的dfs序与时间戳。

如下图有两条重链:
第一条重链为FHKL 他们的开始结点都为F
第二条重链为CD 他们的开始结点都为C
这个标记有什么用处,到后面就明白了。
在这里插入图片描述
我们再来看这个例题:

P3384 【模板】轻重链剖分
在这里插入图片描述
对于操作一和操作二,我们可以知道,树上点与点之间的路径是唯一的(不重复走的情况下)。
所以我们可以得知:

引理:除根节点外的任何一个结点的父亲结点都一定在一条重链上。
证明:因为父亲结点存在儿子,所以一定存在重儿子,所以一定在一条重链上。

所以:
对于x-y之间的路径:是由重链的一部分和叶子节点组成的。
那么我们对于不同的路径情况分类讨论一下即可。
1.假设x、y在一条重链上,那么情况就变得简单了(因为重链上的序号是连续的)。
我们直接把x和y对应的dfs序拿出来在线段树上操作一下即可。
那么问题是如何判断x、y是否在一条重链上呢?
标记重链的开始结点。 这一个操作用上了,只要查询他们两个值的开始结点相同,那么即为同一重链。
2.如果不在一条重链上呢?
那么我们可以维护两个指针分别指向两个结点。
不停的让所在链的顶部结点深度较深那个指针沿着重链往上跳(直接跳到重链开始结点),顺便在线段树上维护操作(因为重链上的结点是连续的)。
到达顶部结点后,我们让他跳到顶部结点的父亲结点。然后继续循环如上操作,直到两指针相遇(或者到了同一条重链上)。

代码:

/*Keep on going Never give up*/
//#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#define endl '\n'
//#define int long long
using namespace std;
const int maxn=1e6+10;
int mod=998244353;

struct node{
    int to,next;
}edge[maxn];

int tot,head[maxn];

void init(){
    memset(head,-1,sizeof head);
}

void adde(int u,int v){
    edge[tot].to=v,edge[tot].next=head[u];
    head[u]=tot++;
    edge[tot].to=u,edge[tot].next=head[v];
    head[v]=tot++;
}
int v[maxn];
int fa[maxn],dep[maxn],siz[maxn],son[maxn];

void dfs1(int u,int f){
    fa[u]=f;  //标记父亲
    dep[u]=dep[f]+1;  //标记深度
    siz[u]=1; //初始化大小
    for(int i=head[u];~i;i=edge[i].next){
        int v=edge[i].to;
        if(v==f) continue;
        dfs1(v,u);
        siz[u]+=siz[v];  //累加大小
        if(siz[v]>siz[son[u]]){  //找重儿子
            son[u]=v;
        }
    }
}
int cnt,dfn[maxn],top[maxn],w[maxn];
void dfs2(int u,int t){
    dfn[u]=++cnt;
    top[u]=t;
    w[cnt]=v[u];  //重新把结点的值转移到连续序列上
    if(!son[u]) return ;
    dfs2(son[u],t);  //有限递归重儿子
    for(int i=head[u];~i;i=edge[i].next){
        int v=edge[i].to;
        if(v==fa[u]||v==son[u]) continue ;
        dfs2(v,v);  //头节点为当前结点开始递归
    }
}
int tree[maxn],lazy[maxn];
void push_down(int node,int l,int r){
    lazy[node*2]+=lazy[node];
    lazy[node*2+1]+=lazy[node];
    int mid=(l+r)/2;
    tree[node*2]+=(mid-l+1)*lazy[node]%mod;
    tree[node*2+1]+=(r-mid)*lazy[node]%mod;
    lazy[node]=0;
}
void build(int node,int start,int ends){
    if(start==ends){
        tree[node]=w[start]%mod;
        return ;
    }
    int mid=(start+ends)/2;
    build(node*2,start,mid);
    build(node*2+1,mid+1,ends);
    tree[node]=(tree[node*2]+tree[node*2+1])%mod;
}

void update(int node,int start,int ends,int l,int r,int val){
    if(start>=l&&ends<=r){
        lazy[node]+=val;
        tree[node]+=(ends-start+1)*val%mod;
        return ;
    }
    if(lazy[node]) push_down(node,start,ends);
    int mid=(start+ends)/2;
    if(l<=mid) update(node*2,start,mid,l,r,val);
    if(mid<r) update(node*2+1,mid+1,ends,l,r,val);
    tree[node]=(tree[node*2]+tree[node*2+1])%mod;
}

int query(int node,int start,int ends,int l,int r){
    if(start>=l&&ends<=r){
        return tree[node]%mod;
    }
    if(lazy[node]) push_down(node,start,ends);
    int mid=(start+ends)/2;
    int res=0;
    if(l<=mid) res+=query(node*2,start,mid,l,r);
    if(mid<r) res+=query(node*2+1,mid+1,ends,l,r);
    return res%mod;
}
int n,m,r;
void mchain(int x,int y,int z){
    z%=mod;
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]]) swap(x,y);
        update(1,1,n,dfn[top[x]],dfn[x],z);
        x=fa[top[x]];
    }
    if(dep[x]>dep[y]) swap(x,y);
    update(1,1,n,dfn[x],dfn[y],z);
}

int qchain(int x,int y){
    int ret=0;
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]]) swap(x,y);
        ret+=query(1,1,n,dfn[top[x]],dfn[x]);
        x=fa[top[x]];
    }
    if(dep[x]>dep[y]) swap(x,y);
    ret+=query(1,1,n,dfn[x],dfn[y]);
    return ret%mod;
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    init();

    cin>>n>>m>>r>>mod;
    for(int i=1;i<=n;i++) cin>>v[i];
    for(int i=0;i<n-1;i++){
        int x,y;
        cin>>x>>y;
        adde(x,y);
    }
    dfs1(r,r);
    dfs2(r,r);
    build(1,1,n);

    for(int i=0;i<m;i++){
        int opt;
        cin>>opt;
        if(opt==1){
            int x,y,z;
            cin>>x>>y>>z;
            mchain(x,y,z);
        }
        else if(opt==2){
            int x,y;
            cin>>x>>y;
            cout<<qchain(x,y)<<endl;
        }
        else if(opt==3){
            int x,y;
            cin>>x>>y;
            update(1,1,n,dfn[x],dfn[x]+siz[x]-1,y);
        }
        else{
            int x;
            cin>>x;
            cout<<query(1,1,n,dfn[x],dfn[x]+siz[x]-1)<<endl;
        }
    }
}











  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值