洛古 P3833 [SHOI2012] 魔法树 题解

题意

题目描述的很清楚了:

有两种操作:

  • 将点 uv 之间的路径上的所有节点的果子个数都加上 d
  • 求前果树中,以点 u 为根的子树中,总共有多少个果子

算法

这道题我们考虑 树链剖分算法 Tree-chain partitioning

就是把一棵树切成一条一条的链。

首先你要会:

  • dfs 序
  • 一种可以维护区间修改区间查询的 DS
  • 树形 DP 求子树大小

有了这些基础东西,我们就可以打出树剖的代码了。

先来看一些定义:

  • 重儿子:父亲节点的所有儿子中子树最大的结点;
  • 轻儿子:父亲节点中除了重儿子以外的儿子;
  • 重边:父亲结点和重儿子连成的边;
  • 重链:由多条重边连接而成的路径;

树链剖分的过程很简单,首先第一遍 DFS 预处理 父亲、深度、大小、重儿子 ;接着,第二遍 DFS 处理出来 dfs 序、连接重链,并且为了用 DS 来维护重链,我们在 DFS 时 保证一条重链上各个节点 dfs 序连续

两遍 DFS 就是树剖的主要处理。

接着对于每一次操作和询问,直接套用 DS 的板子就行了。

具体操作看代码。

代码

关于代码问题,还是劝大家不要想我一样偷懒打 BIT ,很难调,还是 SGT 好。 qwq

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e5+10;
int n,q;
int a[N];
struct Edge{int nxt,to;}e[N*2];
int root=0;
int head[N*2];
int cnt=0;
int dfn=0;
void init(){
    cnt=0;
    for(int i=0;i<N*2;i++)e[i].nxt=-1,head[i]=-1;
}
void addedge(int a,int b){
    e[++cnt].to=b;
    e[cnt].nxt=head[a];
    head[a]=cnt;
}                  //链式前向星
//--------------------------树状数组--------------------------
int lowbit(int x){return x&(-x);}
int tr1[N],tr2[N];
void add(int x,int v) {
   for(int i=x;i<N;i+=lowbit(i))tr1[i]+=v,tr2[i]+=v*x;
}
int query(int x){
    int res=0;
    for(int i=x;i;i-=lowbit(i)) 
        res+=(x+1)*tr1[i]-tr2[i];
    return res;
}
void rangeadd(int l,int r,int v){
    add(l,v);add(r+1,-v);
}
int rangesum(int l,int r){
    return query(r)-query(l-1);
}
//--------------------------树链剖分--------------------------
int f[N];          //父亲节点
int dep[N];        //深度
int siz[N];       //子树大小
int son[N];        //重儿子
int rk[N];         //dfs标号在树上的对应节点
int top[N];        //当前重链顶部
int id[N];         //每个节点剖分后的新编号
void dfs1(int u,int fa){
    siz[u]=1;
    dep[u]=dep[fa]+1;f[u]=fa;
    for(int i=head[u];~i;i=e[i].nxt){
        int v=e[i].to;
        if(v==fa)continue;
        dfs1(v,u);
        siz[u]+=siz[v];
        if(siz[v]>siz[son[u]]||!son[u])son[u]=v; //更新重儿子
    }
}
void dfs2(int u,int t){                 //t:当前重链顶端
    top[u]=t;
    id[u]=++dfn;                        //记录dfs序
    rk[dfn]=u;                          //记录对应节点
    if(!son[u])return;
	dfs2(son[u],t);                     //我们优先进入重儿子,保证一条重链上dfs序连续
    for(int i=head[u];~i;i=e[i].nxt){
        int v=e[i].to;
        if(v!=f[u]&&v!=son[u])dfs2(v,v);//如果不是重儿子,那么另起一条重链
    }
}
void Add(int u,int v,int d){
    while(top[u]!=top[v]){           
        if(dep[top[u]]<dep[top[v]])swap(u,v);// 让链顶更深的先跳
        rangeadd(id[top[u]],id[u],d);
        u=f[top[u]];                        // 跳到链顶的父亲
    }
    if(dep[u]>dep[v])swap(u,v);
    rangeadd(id[u],id[v],d);
}
long long Sum(int u){
    int l=id[u];
    int r=id[u]+siz[u]-1;
    return rangesum(l,r);
}
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin>>n;
    init();
    for(int i=1;i<n;i++){
        int a,b;cin>>a>>b;
        addedge(a,b);
        addedge(b,a);
    }
    cnt=0;
    dfs1(root,0);
    dfs2(root,root);
    cin>>q;
    while(q--){
        char op;cin>>op;
        int u,v,d;
        if(op=='A'){
            cin>>u>>v>>d;
            Add(u,v,d);
        }
        else{
            cin>>u;
            cout<<Sum(u)<<'\n';
        }
    }
    return 0;
}
posted @ 2026-05-30 13:51  Tri_Function  阅读(6)  评论(0)    收藏  举报