• 博客园logo
  • 会员
  • 周边
  • 新闻
  • 博问
  • 闪存
  • 众包
  • 赞助商
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录

yongchaoD

  • 博客园
  • 联系
  • 订阅
  • 管理

公告

View Post

重链剖分/树链剖分

题目来源:https://www.luogu.com.cn/problem/P3384
//
题意:在一颗树上,修改(查询)两点(最短路径)或一点子树上的所有权值。
//
算法:进行剖分:
教程(3个):
https://www.cnblogs.com/chinhhh/p/7965433.html
【《树链剖分:重链剖分》】https://www.bilibili.com/video/BV1qv421C7BE?vd_source=edd8d483423d58308aefa72fbec9bd22
【D11 树链剖分 P3379【模板】最近公共祖先(LCA)】https://www.bilibili.com/video/BV1tY4y1G7em?vd_source=edd8d483423d58308aefa72fbec9bd22

剖分后所有重链上的新节点编号都是连续的,一个节点子树的编号都是连续的。
1:两个dfs的作用
2:build建树
3:在两节点之间的查询(修改),需要进行跳点,x始终保持为所在链顶端的深度更深的那个点,知道两个点处于同一条链上,然后区间查询query(dfn[x],dfn[y])。
注意qRange函数:两点之间的查询,是选择深度更深的那个点,然后区间查询[ dfn[top[x]],dfn[x] ],知道最后在一个重链上,然后还是让x保持 深度小的点,区间查询 [ dfn[x],dfn[y] ],我个人认为一直让x保持深度更深的位置,因为区间查询更方便,[x,y]区间两点一定是x<=y的,跳点也方便。
//
题解:题解注释也很清楚

点击查看代码
//树链剖分
#include<bits/stdc++.h>
#define int long long
#define lp p<<1
#define rp p<<1 |1
using namespace std;
const int N=1e5+9;
vector<int>w(N),fa(N),siz(N),son(N),dep(N),top(N),dfn(N),num(N),tree(N<<2),lazy(N<<2);
vector<int>G[N];
int tot;
int n,q,R,Mod;//r根节点

/* fa[]:两点查询的跳点
 * size[u]:u的子树编号都是连续的,用于查询子树的权值和修改
 * son[u]:u节点的重儿子编号,用于dfs2往重链方向递归,得到top[]
 * dep[u]:u节点的深度:用于挑点过程中保证一直是深度更深的节点 在跳点
 * */
void dfs1(int u,int f){//fa[],size[],son[],dep[]
   fa[u]=f,siz[u]=1,dep[u]=dep[f]+1;
    for(auto v:G[u]){
        if(v==f) { continue;}
        dfs1(v,u);
        siz[u]+=siz[v];//统计u的子节点个数
        if(siz[son[u]]<siz[v]){son[u]=v;}//如果u当前的重儿子子树数 小于 v儿子节点的子树总数,u的重儿子更新
    }
}

/* top[u]:u节点所在重链中的顶点,1:用于判断两点是否在同一重链上; 2:跳点
 * dfn[u]:重链剖分各节点的新编号
 * num[u]:新编号映射
 * */

void dfs2(int u,int f){
    dfn[u]=++tot,num[tot]=u,top[u]=f;
    if(!son[u]) return;//u节点没有重儿子了
    dfs2(son[u],f);
     for(auto v:G[u]){//访问轻儿子
         if(v==fa[u] || v==son[u]) {continue;}
         dfs2(v,v);//轻儿子的重链顶点就是自己
     }
}

void pushup(int p){
    tree[p]=(tree[lp]+tree[rp])%Mod;
}

void pushdown(int p,int len){
    if(lazy[p]!=0){
        lazy[lp]+=lazy[p]%Mod,lazy[rp]+=lazy[p]%Mod;
        tree[lp]+=(len-(len>>1))*lazy[p]%Mod,tree[rp]+=(len>>1)*lazy[p]%Mod;
        lazy[p]=0;
    }
}

void build(int s,int t,int p){
    if(s==t){
        tree[p]=w[num[s]]%Mod;//权值还是存在原节点,要映射回去
        return;
    }
    int m=(s+t)>>1;
    build(s,m,lp),build(m+1,t,rp);
    pushup(p);
}

int query(int l,int r,int s,int t,int p){
    if(l<=s && r>=t){
        return tree[p]%Mod;
    }
    pushdown(p,t-s+1);
    int m=(s+t)>>1,sum=0;
    if(l<=m) {sum=query(l,r,s,m,lp)%Mod;}
    if(r>=m+1) {sum+=query(l,r,m+1,t,rp)%Mod;}
    pushup(p);
    return sum%Mod;
}

void update(int l,int r,int k,int s,int t,int p){
    if(l<=s && r>=t){
        lazy[p]+=k%Mod;
        tree[p]+=(t-s+1)*k%Mod;
        return;
    }
    pushdown(p,t-s+1);
    int m=(s+t)>>1;
    if(l<=m) {update(l,r,k,s,m,lp);}
    if(r>=m+1) {update(l,r,k,m+1,t,rp);}
    pushup(p);
}

int qRange(int x,int y){//跳点查询
    int sum=0;
    while(top[x]!=top[y]){//不在同重链上,一直跳
        if(dep[top[x]]<dep[top[y]]) {swap(x,y);}//x点保持为 所在链顶端的深度更深的那个点
        sum+=query(dfn[top[x]],dfn[x],1,n,1)%Mod;//sum加上x点到x所在链顶端 这一段区间的点权和
        x=fa[top[x]];//跳点:x跳到x所在链顶端的那个点的上面一个点
    }
    //直到两个点处于一条链上
    if(dep[x]>dep[y]) {swap(x,y);}
    sum+=query(dfn[x],dfn[y],1,n,1)%Mod;
    return sum%Mod;
}

void upRange(int x,int y,int k){
   k%=Mod;
   while(top[x]!=top[y]){
       if(dep[top[x]]<dep[top[y]]) {swap(x,y);}//比较的是 链顶端更深,比较谁深,就要跳到top上比较dep   
       update(dfn[top[x]],dfn[x],k,1,n,1);
       x=fa[top[x]];//跳点
   }
   if(dep[x]>dep[y]) {swap(x,y);}
   update(dfn[x],dfn[y],k,1,n,1);
}

signed main()
{
    cin>>n>>q>>R>>Mod;
    for(int i=1;i<=n;i++){cin>>w[i];}//节点权值

    int u,v;
    for(int i=1;i<=n-1;i++){
        cin>>u>>v;
        G[u].push_back(v);
        G[v].push_back(u);
    }
     dfs1(R,R);
     dfs2(R,R);
     build(1,n,1);

     int op,x,y,z;
     while(q--){
       cin>>op;
       if(op==1){//x到y的dfs路 权值+z
         cin>>x>>y>>z;
         upRange(x,y,z);
       }
       else if(op==2){//查询x到y的dfs路 权值和
         cin>>x>>y;
         cout<<qRange(x,y)<<'\n';
       }
       else if(op==3){//x子树都加上权值z
           cin>>x>>z;
           update(dfn[x],dfn[x]+siz[x]-1,z,1,n,1);
       }
       else if(op==4){//查询x子树的权值
          cin>>x;
           cout<<query(dfn[x],dfn[x]+siz[x]-1,1,n,1)<<'\n';//dfn[x]+siz[x]-1:u子树最后连续编号
       }
     }
    return 0;
}

posted on 2024-08-02 12:30  yongchaoD  阅读(10)  评论(0)    收藏  举报

刷新页面返回顶部
 
博客园  ©  2004-2026
浙公网安备 33010602011771号 浙ICP备2021040463号-3