• 【暖*墟】#树链剖分# 树链剖分学习与练习


    树链剖分

    树链剖分是一种优化,将树上最常经过的几条链划为重点,用线段树来优化区间修改和查询。

    并且因为在一棵子树中dfs序是连续的,并且在任意一条重链上,dfs序也是连续的,

    可以认为轻链是单点修改,重链是区间修改,轻重分明,时间复杂度O(Nlog2N)。

    【概念简述】

      即如图所示:   

    即:

    【原理分析】

    10->3可以拆成 10->8的重链 + 8->1的轻边 + 1->3的重链

    (1)信息记录在点上,在线段树上直接修改[1,3],[8,10];

    (2)信息记录在边上,在线段树上,用点标识父边,即:[2,3],[9,10],单点8 的修改。

    【具体操作】

    如上图,1、2、3、4、5的 top 是1;8、9、10的 top 是8;其他的 top 都是自己。

    记录 top 信息,确定一条链的重链轻边之后:1.选 top 大的点向上跳;

    2.每次跳到重链顶端或一条轻边;3.直到两个点在同一重链上。

    根据两遍dfs得到的信息 --> 初始化线段树。

    (1)第一次dfs,求子树大小size[ ],深度dep[ ],重儿子son[ ]。

     

    (2)第二次 dfs,id[x] 记录树链剖分之后的 dfs 序。

    若有重儿子,优先 dfs 传递到底;若是轻边,每个轻边的子节点的 top 都是自己。

    目的:求出 top(划分轻重链)、确定 dfs 序。

    (3)query 函数:查询区间(链)信息。

    深度大的节点向上跳,每次跳某个轻边或者跳完整个重链。

    其中信息经过 get 函数的线段树方式处理,可以实现区间维护。

    【代码实现】

    //--------树链剖分部分----------//
    
    void dfs1(ll u,ll fa_){ //第一遍dfs:求子树大小和重儿子
        siz[u]=1,fa[u]=fa_,dep[u]=dep[fa_]+1;
        for(ll i=head[u];i;i=e[i].nextt){
            if(e[i].ver==fa_) continue;
            dfs1(e[i].ver,u),siz[u]+=siz[e[i].ver]; //计算size
            if(siz[e[i].ver]>siz[son[u]]) son[u]=e[i].ver; //重儿子
        }
    }
    
    void dfs2(ll u,ll fa_){ //第二遍dfs:确定dfs序和top值
        if(son[u]){ //先走重儿子,使重链上的各节点在线段树中的编号连续
            seg[son[u]]=++seg[0]; //节点编号、记入线段树中
            rev[seg[0]]=son[u]; //记录对应的原始编号
            top[son[u]]=top[u],dfs2(son[u],u); 
            //↑↑此位置是已有的重链的节点,更新top值,继续递归
        } for(ll i=head[u];i;i=e[i].nextt){ //递归轻边
            if(top[e[i].ver]) continue; //除去u的重儿子或父亲
            seg[e[i].ver]=++seg[0],rev[seg[0]]=e[i].ver; //加入线段树
            top[e[i].ver]=e[i].ver,dfs2(e[i].ver,u);
            //↑↑先递归到的轻边上的点(dep值min),所在重链的top一定是自己
        }
    }
    
    ll query(ll x,ll y){ //路径询问
        ll fx=top[x],fy=top[y],ans=0;
        while(fx!=fy){ //不在同一重链上,选择深度较大的跳到重链top的fa
            if(dep[fx]<dep[fy]) swap(x,y),swap(fx,fy); 
            ans=ans+get(1,1,seg[0],seg[fx],seg[x]); //边跳边统计答案
            x=fa[fx],fx=top[x]; //往上跳、并更新当前所在点的top值(所在重链)
        } if(dep[x]>dep[y]) swap(x,y); //x、y已在同一条重链上 
        ans=ans+get(1,1,seg[0],seg[x],seg[y]); return ans; //直接统计
    }

    【相关练习】

    { PART.1 模板题系列 }

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<string>
    #include<queue>
    #include<vector>
    #include<cmath>
    #include<map>
    using namespace std;
    typedef long long ll;
    
    //【p2590】树的统计
    
    void reads(int &x){ //读入优化(正负整数)
        int fa=1;x=0;char s=getchar();
        while(s<'0'||s>'9'){if(s=='-')fa=-1;s=getchar();}
        while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
        x*=fa; //正负号
    }
    
    const int N=60019,M=200019;
    
    int n,m,a[N],sumn,maxn,tot=0,head[N];
    
    int siz[N],son[N],top[N],dep[N],fa[N];
    
    int seg[N],rev[M],sum[M],Max[M];
    
    struct node{ int nextt,ver,w; }e[N];
    
    void add(int x,int y)
     { e[++tot].ver=y,e[tot].nextt=head[x],head[x]=tot; }
    
    //--------线段树部分----------\
    
    void build(int rt,int l,int r){ int mid=(l+r)>>1;
        if(l==r){ Max[rt]=sum[rt]=a[rev[l]]; return; } //叶子节点
        build(rt<<1,l,mid),build((rt<<1)+1,mid+1,r); //左右子树
        sum[rt]=sum[rt<<1]+sum[(rt<<1)+1];//更新相关值 
        Max[rt]=max(Max[rt<<1],Max[(rt<<1)+1]); 
    }
    
    void change(int rt,int l,int r,int v,int x){ //单点修改
        if((x>r)||(x<l)) return; //x超出范围
        if((l==r)&&(r==x)){ //到达叶子节点x,开始修改
            sum[rt]=v,Max[rt]=v; return; 
        } int mid=(l+r)>>1;
        if(mid>=x) change(rt<<1,l,mid,v,x); //左儿子
        if(mid+1<=x) change((rt<<1)+1,mid+1,r,v,x); //右儿子 
        sum[rt]=sum[rt<<1]+sum[(rt<<1)+1];//更新相关的值 
        Max[rt]=max(Max[rt<<1],Max[(rt<<1)+1]);
    }
    
    void get(int rt,int l,int r,int x,int y){
    //区间询问,rt是节点标号,l、r是当前区间,x、y是询问区间 
        if((x>r)||(y<l)) return; //与询问区间无交集 
        if((x<=l)&&(r<=y)) //询问区间包含于当前区间 
         { sumn+=sum[rt],maxn=max(maxn,Max[rt]); return; }
        int mid=(l+r)>>1;
        if(mid>=x) get(rt<<1,l,mid,x,y); //左儿子 
        if(mid+1<=y) get((rt<<1)+1,mid+1,r,x,y); //右儿子 
    }
    
    //--------树链剖分部分----------\
    
    void dfs1(int u,int fa_){ //第一遍dfs:求子树大小和重儿子
        siz[u]=1,fa[u]=fa_,dep[u]=dep[fa_]+1;
        for(int i=head[u];i;i=e[i].nextt){
            if(e[i].ver==fa_) continue;
            dfs1(e[i].ver,u),siz[u]+=siz[e[i].ver]; //计算size
            if(siz[e[i].ver]>siz[son[u]]) son[u]=e[i].ver; //重儿子
        }
    }
    
    void dfs2(int u,int fa_){ //第二遍dfs:确定dfs序和top值
        if(son[u]){ //先走重儿子,使重链在线段树中的位置连续
            seg[son[u]]=++seg[0]; //节点记入线段树中
            rev[seg[0]]=son[u]; //记录对应的原始编号
            top[son[u]]=top[u],dfs2(son[u],u); //更新top值
        } for(int i=head[u];i;i=e[i].nextt){
            if(top[e[i].ver]) continue; //除去u的重儿子或父亲
            seg[e[i].ver]=++seg[0],rev[seg[0]]=e[i].ver; //加入线段树
            top[e[i].ver]=e[i].ver,dfs2(e[i].ver,u); //轻边上的点
        }
    }
    
    void query(int x,int y){ //路径询问
        int fx=top[x],fy=top[y];
        while(fx!=fy){ //↓↓选择深度较大的
            if(dep[fx]<dep[fy]) swap(x,y),swap(fx,fy); 
            get(1,1,seg[0],seg[fx],seg[x]);
            x=fa[fx],fx=top[x]; //往上跳、并更新此点的top值
        } if(dep[x]>dep[y]) swap(x,y); //x、y已在同一条重链上 
        get(1,1,seg[0],seg[x],seg[y]); 
    }
    
    //--------主程序部分----------\
    
    int main(){ 
        int u,v; reads(n);
        for(int i=1;i<n;i++)
            reads(u),reads(v),add(u,v),add(v,u);
        for(int i=1;i<=n;i++) reads(a[i]);
        dfs1(1,0),seg[0]=seg[1]=top[1]=rev[1]=1; //设1为根结点
        dfs2(1,0),build(1,1,seg[0]); //建立线段树
        reads(m); char ss[10];
        for(int i=1;i<=m;i++){
            scanf("%s",ss+1); reads(u),reads(v);
            if(ss[1]=='C') change(1,1,seg[0],v,seg[u]); //单点修改
            else{ sumn=0,maxn=-10000000,query(u,v); //询问
                if(ss[2]=='M') printf("%d
    ",maxn);
                else printf("%d
    ",sumn);
            }
        }
    }
    洛谷p2590 //单点修改 + 路径max + 路径sum
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<string>
    #include<queue>
    #include<vector>
    #include<cmath>
    #include<map>
    using namespace std;
    typedef long long ll;
    
    /*【p3178】树上操作 
    有一棵点数为 N 的树,以点1为根,且有边权。M 个操作:
    1:把某个节点 x 的点权增加 a(单点修改)
    2:把某个节点 x 为根的子树中所有点的点权都增加 a(区间修改)
    3:询问某个节点 x 到根的路径中所有点的点权和(区间查询) */
    
    void reads(ll &x){ //读入优化(正负整数)
        ll fa=1;x=0;char s=getchar();
        while(s<'0'||s>'9'){if(s=='-')fa=-1;s=getchar();}
        while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
        x*=fa; //正负号
    }
    
    const ll N=1000019;
    
    ll n,m,a[N*2],tot=0,head[N*2];
    
    ll siz[N*2],son[N*2],top[N*2],dep[N*2],fa[N*2];
    
    ll rev[N*2],seg[N*4],sum[N*4],lazy[N*4];
    
    struct node{ ll nextt,ver,w; }e[N*2];
    
    void add(ll x,ll y)
     { e[++tot].ver=y,e[tot].nextt=head[x],head[x]=tot; }
    
    //--------线段树部分----------//
    
    void build(ll rt,ll l,ll r){ ll mid=(l+r)>>1;
        if(l==r){ sum[rt]=a[rev[l]]; return; } //叶子节点
        build(rt<<1,l,mid),build(rt<<1|1,mid+1,r); //左右子树
        sum[rt]=sum[rt<<1]+sum[rt<<1|1];
    }
    
    void PushDown(ll rt,ll l,ll r){ //标记下移
        if(!lazy[rt]) return; ll mid=(l+r)>>1;
        sum[rt<<1]+=lazy[rt]*(mid-l+1);
        sum[rt<<1|1]+=lazy[rt]*(r-mid);
        lazy[rt<<1]+=lazy[rt],lazy[rt<<1|1]+=lazy[rt];
        lazy[rt]=0; //此点标记清零
    }
    
    void change(ll rt,ll l,ll r,ll v,ll x,ll y){ //区间修改
        if((x>r)||(y<l)) return; //不相交区间
        if((x<=l)&&(r<=y)) //此区间完全被询问区间包含
         { sum[rt]+=v*(r-l+1),lazy[rt]+=v; return; }
        ll mid=(l+r)>>1; PushDown(rt,l,r);
        change(rt<<1,l,mid,v,x,y),change(rt<<1|1,mid+1,r,v,x,y);
        sum[rt]=sum[rt<<1]+sum[rt<<1|1];
    }
    
    ll get(ll rt,ll l,ll r,ll x,ll y){
    //区间询问,rt是节点标号,l、r是当前区间,x、y是询问区间 
        if((x>r)||(y<l)) return 0; //与询问区间无交集 
        if((x<=l)&&(r<=y)) return sum[rt];
        ll mid=(l+r)>>1; PushDown(rt,l,r);
        return get(rt<<1,l,mid,x,y)+get(rt<<1|1,mid+1,r,x,y);
    }
    
    //--------树链剖分部分----------//
    
    void dfs1(ll u,ll fa_){ //第一遍dfs:求子树大小和重儿子
        siz[u]=1,fa[u]=fa_,dep[u]=dep[fa_]+1;
        for(ll i=head[u];i;i=e[i].nextt){
            if(e[i].ver==fa_) continue;
            dfs1(e[i].ver,u),siz[u]+=siz[e[i].ver]; //计算size
            if(siz[e[i].ver]>siz[son[u]]) son[u]=e[i].ver; //重儿子
        }
    }
    
    void dfs2(ll u,ll fa_){ //第二遍dfs:确定dfs序和top值
        if(son[u]){ //先走重儿子,使重链在线段树中的位置连续
            seg[son[u]]=++seg[0]; //节点记入线段树中
            rev[seg[0]]=son[u]; //记录对应的原始编号
            top[son[u]]=top[u],dfs2(son[u],u); //更新top值
        } for(ll i=head[u];i;i=e[i].nextt){
            if(top[e[i].ver]) continue; //除去u的重儿子或父亲
            seg[e[i].ver]=++seg[0],rev[seg[0]]=e[i].ver; //加入线段树
            top[e[i].ver]=e[i].ver,dfs2(e[i].ver,u); //轻边上的点
        }
    }
    
    ll query(ll x,ll y){ //路径询问
        ll fx=top[x],fy=top[y],ans=0;
        while(fx!=fy){ //↓↓选择深度较大的
            if(dep[fx]<dep[fy]) swap(x,y),swap(fx,fy); 
            ans=ans+get(1,1,seg[0],seg[fx],seg[x]);
            x=fa[fx],fx=top[x]; //往上跳、并更新此点的top值
        } if(dep[x]>dep[y]) swap(x,y); //x、y已在同一条重链上 
        ans=ans+get(1,1,seg[0],seg[x],seg[y]); return ans;
    }
    
    //--------主程序部分----------//
    
    int main(){
        ll u,v,op; reads(n),reads(m);
        for(ll i=1;i<=n;i++) reads(a[i]);
        for(ll i=1;i<n;i++) 
            reads(u),reads(v),add(u,v),add(v,u);
        dfs1(1,0),seg[0]=seg[1]=top[1]=rev[1]=1; //设1为根结点
        dfs2(1,0),build(1,1,seg[0]); //建立线段树
        for(ll i=1;i<=m;i++){ reads(op),reads(u);
            if(op==1) reads(v),change(1,1,seg[0],v,seg[u],seg[u]);
            if(op==2) reads(v),change(1,1,n,v,seg[u],seg[u]+siz[u]-1);
            if(op==3) printf("%lld
    ",query(1,u));
        }
    }
    洛谷p3178 //单点修改 + 子树修改 + 路径sum
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<string>
    #include<queue>
    #include<vector>
    #include<cmath>
    #include<map>
    using namespace std;
    typedef long long ll;
    
    /*【p3384】【模板】树链剖分
    路径修改 + 路径sum + 子树修改 + 子树sum */
    
    void reads(ll &x){ //读入优化(正负整数)
        ll fa=1;x=0;char s=getchar();
        while(s<'0'||s>'9'){if(s=='-')fa=-1;s=getchar();}
        while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
        x*=fa; //正负号
    }
    
    const ll N=1000019;
    
    ll n,m,a[N*2],tot=0,head[N*2],root,mod;
    
    ll siz[N*2],son[N*2],top[N*2],dep[N*2],fa[N*2];
    
    ll rev[N*2],id[N*4],sum[N*4],lazy[N*4];
    
    struct node{ ll nextt,ver,w; }e[N*2];
    
    void add(ll x,ll y)
     { e[++tot].ver=y,e[tot].nextt=head[x],head[x]=tot; }
    
    //--------线段树部分----------//
    
    void build(ll rt,ll l,ll r){ ll mid=(l+r)>>1;
        if(l==r){ sum[rt]=a[rev[l]]%mod; return; } //叶子节点
        build(rt<<1,l,mid),build(rt<<1|1,mid+1,r); //左右子树
        sum[rt]=(sum[rt<<1]+sum[rt<<1|1])%mod;
    }
    
    void PushDown(ll rt,ll l,ll r){ //标记下移
        if(!lazy[rt]) return; ll mid=(l+r)>>1;
        (sum[rt<<1]+=lazy[rt]*(mid-l+1)%mod)%=mod;
        (sum[rt<<1|1]+=lazy[rt]*(r-mid)%mod)%=mod;
        (lazy[rt<<1]+=lazy[rt])%=mod, //↓↓此点标记清零
        (lazy[rt<<1|1]+=lazy[rt])%=mod; lazy[rt]=0;
    }
    
    void update(ll rt,ll l,ll r,ll x,ll y,ll v){ //区间修改
        if((x>r)||(y<l)) return; //不相交区间
        if((x<=l)&&(r<=y)) //此区间完全被询问区间包含
         { sum[rt]+=v*(r-l+1),lazy[rt]+=v; return; }
        ll mid=(l+r)>>1; PushDown(rt,l,r);
        update(rt<<1,l,mid,x,y,v),update(rt<<1|1,mid+1,r,x,y,v);
        sum[rt]=(sum[rt<<1]+sum[rt<<1|1])%mod;
    }
    
    ll query(ll rt,ll l,ll r,ll x,ll y){
    //区间询问,rt是节点标号,l、r是当前区间,x、y是询问区间 
        if((x>r)||(y<l)) return 0; //与询问区间无交集 
        if((x<=l)&&(r<=y)) return sum[rt]%mod;
        ll mid=(l+r)>>1; PushDown(rt,l,r);
        return (query(rt<<1,l,mid,x,y)+query(rt<<1|1,mid+1,r,x,y))%mod;
    }
    
    //--------树链剖分部分----------//
    
    void dfs1(ll u,ll fa_){ //第一遍dfs:求子树大小和重儿子
        siz[u]=1,fa[u]=fa_,dep[u]=dep[fa_]+1;
        for(ll i=head[u];i;i=e[i].nextt){
            if(e[i].ver==fa_) continue;
            dfs1(e[i].ver,u),siz[u]+=siz[e[i].ver]; //计算size
            if(siz[e[i].ver]>siz[son[u]]) son[u]=e[i].ver; //重儿子
        }
    }
    
    void dfs2(ll u,ll fa_){ //第二遍dfs:确定dfs序和top值
        if(son[u]){ //先走重儿子,使重链在线段树中的位置连续
            id[son[u]]=++id[0]; //节点记入线段树中
            rev[id[0]]=son[u]; //记录对应的原始编号
            top[son[u]]=top[u],dfs2(son[u],u); //更新top值
        } for(ll i=head[u];i;i=e[i].nextt){
            if(top[e[i].ver]) continue; //除去u的重儿子或父亲
            id[e[i].ver]=++id[0],rev[id[0]]=e[i].ver; //加入线段树
            top[e[i].ver]=e[i].ver,dfs2(e[i].ver,u); //轻边上的点
        }
    }
    
    ll q_route(ll x,ll y){ //【路径查询】
        ll ans=0; while(top[x]!=top[y]){
            if(dep[top[x]]<dep[top[y]]) swap(x,y);
            (ans+=query(1,1,n,id[top[x]],id[x]))%=mod; x=fa[top[x]];
        } if(dep[x]>dep[y]) swap(x,y); 
        ans+=query(1,1,n,id[x],id[y]); return ans%mod;
    }
    
    void upd_route(ll x,ll y,ll k){ 
        k%=mod; while(top[x]!=top[y]){
            if(dep[top[x]]<dep[top[y]]) swap(x,y);
            update(1,1,n,id[top[x]],id[x],k); x=fa[top[x]];
        } if(dep[x]>dep[y]) swap(x,y); update(1,1,n,id[x],id[y],k);
    }
    
    //【子树查询】子树区间右端点为id[x]+siz[x]-1,直接求值/修改即可
    
    ll q_son(ll x){ return query(1,1,n,id[x],id[x]+siz[x]-1); } 
    
    void upd_son(ll x,ll k){ update(1,1,n,id[x],id[x]+siz[x]-1,k); }
    
    
    //--------主程序部分----------//
    
    int main(){
        reads(n),reads(m),reads(root),reads(mod);
        for(ll i=1;i<=n;i++) reads(a[i]),a[i]%=mod;
        for(ll i=1,u,v;i<n;i++) 
            reads(u),reads(v),add(u,v),add(v,u);
        dfs1(root,0),top[root]=root,rev[1]=root,id[root]=id[0]=1,
        dfs2(root,0),build(1,1,id[0]);
        for(ll i=1,op,u,v,w;i<=m;i++){ reads(op),reads(u);
            if(op==1) reads(v),reads(w),upd_route(u,v,w);
            if(op==2) reads(v),printf("%lld
    ",q_route(u,v));
            if(op==3) reads(v),upd_son(u,v);
            if(op==4) printf("%lld
    ",q_son(u));
        }
    }
    
    //这题硬是调了一个小时...结果发现query函数的mod没写...
    //(query(rt<<1,l,mid,x,y)+query(rt<<1|1,mid+1,r,x,y))%mod;
    //这个故事告诉我们,还是要随时mod%%%dalao啊qwq
    p3384【模板】树链剖分 路径修改 + 路径sum + 子树修改 + 子树sum
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<string>
    #include<queue>
    #include<vector>
    #include<cmath>
    #include<map>
    using namespace std;
    typedef long long ll;
    
    /*【p2146】软件包管理器 
    服务器的依赖关系是一棵树。根节点为0号软件。
    每次安装软件,就把根节点到x软件路径上的值全部变为1。
    每次卸载软件,就把x以及它的子树的值变为0。
    求每次操作影响了的软件总数。 */
    
    //用区间和的思想,每次操作之前记录一下sum[root]的值,
    //更新之后再查询一遍sum[root]的值,两者之差的绝对值则为答案。
    
    void reads(ll &x){ //读入优化(正负整数)
        ll fa=1;x=0;char s=getchar();
        while(s<'0'||s>'9'){if(s=='-')fa=-1;s=getchar();}
        while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
        x*=fa; //正负号
    }
    
    const ll N=1000019;
    
    ll n,m,a[N*2],tot=0,head[N*2];
    
    ll siz[N*2],son[N*2],top[N*2],dep[N*2],fa[N*2];
    
    ll rev[N*2],seg[N*4],sum[N*4],lazy[N*4];
    
    struct node{ ll nextt,ver,w; }e[N*2];
    
    void add(ll x,ll y)
     { e[++tot].ver=y,e[tot].nextt=head[x],head[x]=tot; }
    
    //--------线段树部分----------//
    
    void build(ll rt,ll l,ll r){
        lazy[rt]=-1,sum[rt]=0; if(l==r) return; //叶子节点
        ll mid=(l+r)>>1; build(rt<<1,l,mid),build(rt<<1|1,mid+1,r); }
    
    void PushDown(ll rt,ll l,ll r){ //标记下移
        ll mid=(l+r)>>1; sum[rt<<1]=lazy[rt]*(mid-l+1);
        sum[rt<<1|1]=lazy[rt]*(r-mid);
        lazy[rt<<1]=lazy[rt<<1|1]=lazy[rt],lazy[rt]=-1; }
    
    void update(ll rt,ll l,ll r,ll v,ll x,ll y){ //区间修改
        if((x>r)||(y<l)) return; //不相交区间
        if((x<=l)&&(r<=y)) //此区间完全被询问区间包含
         { sum[rt]=v*(r-l+1),lazy[rt]=v; return; }
        ll mid=(l+r)>>1; if(lazy[rt]!=-1) PushDown(rt,l,r);
        update(rt<<1,l,mid,v,x,y),update(rt<<1|1,mid+1,r,v,x,y);
        sum[rt]=sum[rt<<1]+sum[rt<<1|1];
    }
    
    //--------树链剖分部分----------//
    
    void dfs1(ll u,ll fa_){ //第一遍dfs:求子树大小和重儿子
        siz[u]=1,fa[u]=fa_,dep[u]=dep[fa_]+1;
        for(ll i=head[u];i;i=e[i].nextt){
            if(e[i].ver==fa_) continue;
            dfs1(e[i].ver,u),siz[u]+=siz[e[i].ver]; //计算size
            if(siz[e[i].ver]>siz[son[u]]) son[u]=e[i].ver; //重儿子
        }
    }
    
    void dfs2(ll u,ll fa_){ //第二遍dfs:确定dfs序和top值
        if(son[u]){ //先走重儿子,使重链在线段树中的位置连续
            seg[son[u]]=++seg[0]; //节点记入线段树中
            rev[seg[0]]=son[u]; //记录对应的原始编号
            top[son[u]]=top[u],dfs2(son[u],u); //更新top值
        } for(ll i=head[u];i;i=e[i].nextt){
            if(top[e[i].ver]) continue; //除去u的重儿子或父亲
            seg[e[i].ver]=++seg[0],rev[seg[0]]=e[i].ver; //加入线段树
            top[e[i].ver]=e[i].ver,dfs2(e[i].ver,u); //轻边上的点
        }
    }
    
    void change(ll x,ll y,ll v){
        while(top[x]!=top[y]){ //↓↓选择深度较大的
            if(dep[top[x]]<dep[top[y]]) swap(x,y); 
            update(1,1,seg[0],v,seg[top[x]],seg[x]);
            x=fa[top[x]]; //往上跳、并更新此点的top值
        } if(dep[x]>dep[y]) swap(x,y); //x、y已在同一条重链上 
        update(1,1,seg[0],v,seg[x],seg[y]);
    }
    
    //--------主程序部分----------//
    
    int main(){ //↓↓把1号节点设为根节点
        reads(n); for(ll i=2,x;i<=n;i++) reads(x),add(x+1,i);
        dfs1(1,0),seg[0]=seg[1]=top[1]=rev[1]=1; //设1为根结点
        dfs2(1,0); reads(m); char op[19]; build(1,1,seg[0]);
        for(ll i=1,lastt,x;i<=m;i++){ 
            cin>>op; reads(x); x++; lastt=sum[1];
            if(op[0]=='i') change(1,x,1),printf("%lld
    ",abs(sum[1]-lastt));
            if(op[0]=='u') update(1,1,seg[0],0,seg[x],seg[x]+siz[x]-1),
                printf("%lld
    ",abs(sum[1]-lastt));
        }
    }
    p2146 软件包管理器 //路径修改 + 子树修改
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<string>
    #include<queue>
    #include<vector>
    #include<cmath>
    #include<map>
    using namespace std;
    typedef long long ll;
    
    /*【p3833】魔法树 //路径修改 + 子树sum */
    
    void reads(ll &x){ //读入优化(正负整数)
        ll fa=1;x=0;char s=getchar();
        while(s<'0'||s>'9'){if(s=='-')fa=-1;s=getchar();}
        while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
        x*=fa; //正负号
    }
    
    const ll N=1000019;
    
    ll n,m,a[N*2],tot=0,head[N*2];
    
    ll siz[N*2],son[N*2],top[N*2],dep[N*2],fa[N*2];
    
    ll rev[N*2],id[N*4],sum[N*4],lazy[N*4];
    
    struct node{ ll nextt,ver,w; }e[N*2];
    
    void add(ll x,ll y)
     { e[++tot].ver=y,e[tot].nextt=head[x],head[x]=tot; }
    
    //--------线段树部分----------//
    
    void build(ll rt,ll l,ll r){ ll mid=(l+r)>>1;
        if(l==r){ sum[rt]=a[rev[l]]; return; } //叶子节点
        build(rt<<1,l,mid),build(rt<<1|1,mid+1,r); //左右子树
        sum[rt]=(sum[rt<<1]+sum[rt<<1|1]);
    }
    
    void PushDown(ll rt,ll l,ll r){ //标记下移
        if(!lazy[rt]) return; ll mid=(l+r)>>1;
        sum[rt<<1]+=lazy[rt]*(mid-l+1);
        sum[rt<<1|1]+=lazy[rt]*(r-mid);
        lazy[rt<<1]+=lazy[rt], //↓↓此点标记清零
        lazy[rt<<1|1]+=lazy[rt]; lazy[rt]=0;
    }
    
    void update(ll rt,ll l,ll r,ll x,ll y,ll v){ //区间修改
        if((x>r)||(y<l)) return; //不相交区间
        if((x<=l)&&(r<=y)) //此区间完全被询问区间包含
         { sum[rt]+=v*(r-l+1),lazy[rt]+=v; return; }
        ll mid=(l+r)>>1; PushDown(rt,l,r);
        update(rt<<1,l,mid,x,y,v),update(rt<<1|1,mid+1,r,x,y,v);
        sum[rt]=(sum[rt<<1]+sum[rt<<1|1]);
    }
    
    ll query(ll rt,ll l,ll r,ll x,ll y){
    //区间询问,rt是节点标号,l、r是当前区间,x、y是询问区间 
        if((x>r)||(y<l)) return 0; //与询问区间无交集 
        if((x<=l)&&(r<=y)) return sum[rt];
        ll mid=(l+r)>>1; PushDown(rt,l,r);
        return (query(rt<<1,l,mid,x,y)+query(rt<<1|1,mid+1,r,x,y));
    }
    
    //--------树链剖分部分----------//
    
    void dfs1(ll u,ll fa_){ //第一遍dfs:求子树大小和重儿子
        siz[u]=1,fa[u]=fa_,dep[u]=dep[fa_]+1;
        for(ll i=head[u];i;i=e[i].nextt){
            if(e[i].ver==fa_) continue;
            dfs1(e[i].ver,u),siz[u]+=siz[e[i].ver]; //计算size
            if(siz[e[i].ver]>siz[son[u]]) son[u]=e[i].ver; //重儿子
        }
    }
    
    void dfs2(ll u,ll fa_){ //第二遍dfs:确定dfs序和top值
        if(son[u]){ //先走重儿子,使重链在线段树中的位置连续
            id[son[u]]=++id[0]; //节点记入线段树中
            rev[id[0]]=son[u]; //记录对应的原始编号
            top[son[u]]=top[u],dfs2(son[u],u); //更新top值
        } for(ll i=head[u];i;i=e[i].nextt){
            if(top[e[i].ver]) continue; //除去u的重儿子或父亲
            id[e[i].ver]=++id[0],rev[id[0]]=e[i].ver; //加入线段树
            top[e[i].ver]=e[i].ver,dfs2(e[i].ver,u); //轻边上的点
        }
    }
    
    ll q_route(ll x,ll y){ //【路径查询】
        ll ans=0; while(top[x]!=top[y]){
            if(dep[top[x]]<dep[top[y]]) swap(x,y);
            ans+=query(1,1,n,id[top[x]],id[x]); x=fa[top[x]];
        } if(dep[x]>dep[y]) swap(x,y); 
        ans+=query(1,1,n,id[x],id[y]); return ans;
    }
    
    void upd_route(ll x,ll y,ll k){ //(1)
        while(top[x]!=top[y]){
            if(dep[top[x]]<dep[top[y]]) swap(x,y);
            update(1,1,n,id[top[x]],id[x],k); x=fa[top[x]];
        } if(dep[x]>dep[y]) swap(x,y); update(1,1,n,id[x],id[y],k);
    }
    
    //【子树查询】子树区间右端点为id[x]+siz[x]-1,直接求值/修改即可
    
    ll q_son(ll x){ return query(1,1,n,id[x],id[x]+siz[x]-1); } //(2)
    
    void upd_son(ll x,ll k){ update(1,1,n,id[x],id[x]+siz[x]-1,k); }
    
    
    //--------主程序部分----------//
    
    int main(){
        reads(n); for(ll i=1,u,v;i<n;i++) 
            reads(u),reads(v),u++,v++,add(u,v),add(v,u);
        dfs1(1,0),top[1]=rev[1]=id[1]=id[0]=1;
        dfs2(1,0),build(1,1,n); reads(m); char op[19];
        for(ll i=1,u,v,w;i<=m;i++){ cin>>op; reads(u),u++;
            if(op[0]=='A') reads(v),v++,reads(w),upd_route(u,v,w);
            if(op[0]=='Q') printf("%lld
    ",q_son(u));
        }
    }
    p3833 魔法树 //路径修改 + 子树sum

    { PART.2 边权题系列 }

    每个点只有一个父亲结点,可以考虑把 此点和父亲结点连边の边权 放置到 此点的点权

    • 【注意】每条路径上の父亲节点的点权不应该考虑在内。
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<string>
    #include<queue>
    #include<vector>
    #include<cmath>
    #include<map>
    using namespace std;
    typedef long long ll;
    
    /*【p4315】月下毛景树 //边权:单点修改 + 路径修改 + 路径max */
    
    //【处理‘边权’的树链剖分问题】因为一个点最多只有一个父亲结点,
    // 那么,可以考虑把[此点--父亲结点の边权]放置到[此点的点权]。
    
    //【注意】每条路径上の父亲节点的点权不应该考虑在内。
    
    void reads(ll &x){ //读入优化(正负整数)
        ll fa=1;x=0;char s=getchar();
        while(s<'0'||s>'9'){if(s=='-')fa=-1;s=getchar();}
        while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
        x*=fa; //正负号
    }
    
    const ll N=1000019; struct node{ ll nextt,ver,w; }e[N*2];
    
    ll n,m,a[N*2],tot=0,head[N*2],siz[N*2],son[N*2],top[N*2],dep[N*2],fa[N*2];
    
    ll rev[N*2],id[N*4],maxx[N*4],lazy[N*4],tag[N*4]; //修改标记,替换标记
    
    void add(ll x,ll y,ll z)
     { e[++tot].ver=y,e[tot].nextt=head[x],e[tot].w=z,head[x]=tot; }
    
    //--------线段树部分----------//
    
    void build(ll rt,ll l,ll r){ 
        tag[rt]=-1; ll mid=(l+r)>>1;
        if(l==r){ maxx[rt]=a[l]; return; } //叶子节点
        build(rt<<1,l,mid),build(rt<<1|1,mid+1,r); //左右子树
        maxx[rt]=max(maxx[rt<<1],maxx[rt<<1|1]);
    }
    
    void PushDown(ll rt,ll l,ll r){ //标记下移
        ll ls=rt<<1,rs=rt<<1|1;
        if(tag[rt]>=0) lazy[ls]=lazy[rs]=0, //区间替换标记
            maxx[ls]=maxx[rs]=tag[ls]=tag[rs]=tag[rt],tag[rt]=-1;
        if(lazy[rt]) lazy[ls]+=lazy[rt],lazy[rs]+=lazy[rt],
            maxx[ls]+=lazy[rt],maxx[rs]+=lazy[rt],lazy[rt]=0;
    }
    
    void update1(ll rt,ll l,ll r,ll x,ll y,ll v){ //区间替换
        if((x>r)||(y<l)) return; //不相交区间
        if((x<=l)&&(r<=y)) //此区间完全被询问区间包含
         { maxx[rt]=tag[rt]=v,lazy[rt]=0; return; }
        ll mid=(l+r)>>1; PushDown(rt,l,r);
        update1(rt<<1,l,mid,x,y,v),update1(rt<<1|1,mid+1,r,x,y,v);
        maxx[rt]=max(maxx[rt<<1],maxx[rt<<1|1]);
    }
    
    void update2(ll rt,ll l,ll r,ll x,ll y,ll v){ //区间修改
        if((x>r)||(y<l)) return; //不相交区间
        if((x<=l)&&(r<=y)) //此区间完全被询问区间包含
         { maxx[rt]+=v,lazy[rt]+=v; return; }
        ll mid=(l+r)>>1; PushDown(rt,l,r);
        update2(rt<<1,l,mid,x,y,v),update2(rt<<1|1,mid+1,r,x,y,v);
        maxx[rt]=max(maxx[rt<<1],maxx[rt<<1|1]);
    }
    
    ll query(ll rt,ll l,ll r,ll x,ll y){
    //区间询问,rt是节点标号,l、r是当前区间,x、y是询问区间 
        if((x>r)||(y<l)) return 0; //与询问区间无交集 
        if((x<=l)&&(r<=y)) return maxx[rt];
        ll mid=(l+r)>>1; PushDown(rt,l,r);
        return max(query(rt<<1,l,mid,x,y),query(rt<<1|1,mid+1,r,x,y));
    }
    
    //--------树链剖分部分----------//
    
    void dfs1(ll u,ll fa_){ //第一遍dfs:求子树大小和重儿子
        siz[u]=1,fa[u]=fa_,dep[u]=dep[fa_]+1;
        for(ll i=head[u];i;i=e[i].nextt){
            if(e[i].ver==fa_) continue; rev[e[i].ver]=e[i].w;
            dfs1(e[i].ver,u),siz[u]+=siz[e[i].ver];
            if(siz[e[i].ver]>siz[son[u]]) son[u]=e[i].ver; //重儿子
        }
    }
    
    void dfs2(ll u,ll fa_){ //第二遍dfs:确定dfs序和top值
        if(son[u]){ //先走重儿子,使重链在线段树中的位置连续
            id[son[u]]=++id[0]; //节点记入线段树中
            a[id[0]]=rev[son[u]]; //记录对应的原始编号
            top[son[u]]=top[u],dfs2(son[u],u); //更新top值
        } for(ll i=head[u];i;i=e[i].nextt){
            if(top[e[i].ver]) continue; //除去u的重儿子或父亲
            id[e[i].ver]=++id[0],a[id[0]]=rev[e[i].ver]; //加入线段树
            top[e[i].ver]=e[i].ver,dfs2(e[i].ver,u); //轻边上的点
        }
    }
    
    //--------修改&查询操作部分----------//
    
    ll q_route(ll x,ll y){ //【路径查询】//(4)
        ll ans=0; while(top[x]!=top[y]){
            if(dep[top[x]]<dep[top[y]]) swap(x,y);
            ans=max(ans,query(1,1,n,id[top[x]],id[x])); x=fa[top[x]];
        } if(dep[x]>dep[y]) swap(x,y); 
        ans=max(ans,query(1,1,n,id[x]+1,id[y])); return ans;
    }
    
    void upd_route1(ll x,ll y,ll k){ //(2)
        while(top[x]!=top[y]){
            if(dep[top[x]]<dep[top[y]]) swap(x,y);
            update1(1,1,n,id[top[x]],id[x],k); x=fa[top[x]];
        } if(dep[x]>dep[y]) swap(x,y); update1(1,1,n,id[x]+1,id[y],k);
    }
    
    void upd_route2(ll x,ll y,ll k){ //(3)
        while(top[x]!=top[y]){
            if(dep[top[x]]<dep[top[y]]) swap(x,y);
            update2(1,1,n,id[top[x]],id[x],k); x=fa[top[x]];
        } if(dep[x]>dep[y]) swap(x,y); update2(1,1,n,id[x]+1,id[y],k);
    }
    
    //【子树查询】子树区间右端点为id[x]+siz[x]-1,直接求值/修改即可
    
    ll q_son(ll x){ return query(1,1,n,id[x],id[x]+siz[x]-1); }
    
    void upd_son(ll x,ll k){ update1(1,1,n,id[x],id[x]+siz[x]-1,k); }
    
    
    //--------主程序部分----------//
    
    int main(){
        reads(n); for(ll i=1,u,v,w;i<n;i++) 
            reads(u),reads(v),reads(w),add(u,v,w),add(v,u,w);
        dfs1(1,0),top[1]=rev[1]=id[1]=id[0]=1;
        dfs2(1,0),build(1,1,n); char op[19];
        while(1){ cin>>op; if(op[0]=='S') break;
            ll u,v,w; reads(u),reads(v);
            if(op[1]=='h') //↓↓判断(按输入顺序的)第k条树枝的父亲是谁
                u=dep[e[u*2-1].ver]<dep[e[u*2].ver]?e[u*2].ver:e[u*2-1].ver,
                update1(1,1,n,id[u],id[u],v); //单点修改
            if(op[1]=='o') reads(w),upd_route1(u,v,w);
            if(op[1]=='d') reads(w),upd_route2(u,v,w);
            if(op[1]=='a') printf("%lld
    ",q_route(u,v));
        }
    }
    p4315 月下毛景树 //边权:单点修改 + 路径修改 + 路径max
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<string>
    #include<queue>
    #include<vector>
    #include<cmath>
    #include<map>
    using namespace std;
    typedef long long ll;
    
    /*【p1505】旅游 //边权:单点修改 + 路径修改 + 路径min/max/sum */
    
    //【处理‘边权’的树链剖分问题】因为一个点最多只有一个父亲结点,
    // 那么,可以考虑把[此点--父亲结点の边权]放置到[此点的点权]。
    
    //【注意】每条路径上の父亲节点的点权不应该考虑在内。
    
    void reads(ll &x){ //读入优化(正负整数)
        ll fa=1;x=0;char s=getchar();
        while(s<'0'||s>'9'){if(s=='-')fa=-1;s=getchar();}
        while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
        x*=fa; //正负号
    }
    
    const ll N=1000019; struct node{ ll nextt,ver,w; }e[N*2];
    
    ll n,m,a[N*2],tot=1,head[N*2];
    
    ll siz[N*2],son[N*2],top[N*2],dep[N*2],fa[N*2],num[N*2];
    
    ll rev[N*2],id[N*4],maxx[N*4],minn[N*4],sum[N*4],tag[N*4]; //相反数标记
    
    void add(ll x,ll y,ll z)
     { e[++tot].ver=y,e[tot].nextt=head[x],e[tot].w=z,head[x]=tot; }
    
    //--------线段树部分----------//
    
    void build(ll rt,ll l,ll r){ ll mid=(l+r)>>1;
        if(l==r){ sum[rt]=minn[rt]=maxx[rt]=a[l]; return; } //叶子节点
        build(rt<<1,l,mid),build(rt<<1|1,mid+1,r); //左右子树
        maxx[rt]=max(maxx[rt<<1],maxx[rt<<1|1]);
        minn[rt]=min(minn[rt<<1],minn[rt<<1|1]);
        sum[rt]=sum[rt<<1]+sum[rt<<1|1]; //维护三种值
    }
    
    void revs(ll rt){
        maxx[rt]=-maxx[rt],minn[rt]=-minn[rt];
        swap(maxx[rt],minn[rt]);
    }
    
    void PushDown(ll rt){ //标记下移
        ll ls=rt<<1,rs=rt<<1|1;
        if(tag[rt]) sum[ls]=-sum[ls],sum[rs]=-sum[rs],
            revs(ls),revs(rs),tag[ls]^=1,tag[rs]^=1,tag[rt]^=1;
    }
    
    void update(ll rt,ll l,ll r,ll p,ll v){ //单点修改
        if(l==r){ maxx[rt]=minn[rt]=sum[rt]=v; return; }
        ll mid=(l+r)>>1; PushDown(rt);
        if(p<=mid) update(rt<<1,l,mid,p,v);
        else update(rt<<1|1,mid+1,r,p,v);
        maxx[rt]=max(maxx[rt<<1],maxx[rt<<1|1]);
        minn[rt]=min(minn[rt<<1],minn[rt<<1|1]);
        sum[rt]=sum[rt<<1]+sum[rt<<1|1]; //维护三种值
    }
    
    void Reverse(ll rt,ll L,ll R,ll l,ll r){
        if(L==l&&R==r){tag[rt]^=1,sum[rt]=-sum[rt],revs(rt);return;}
        PushDown(rt); ll mid=L+R>>1,ls=rt<<1,rs=rt<<1|1;
        if(r<=mid) Reverse(ls,L,mid,l,r);
        else if(l>mid) Reverse(rs,mid+1,R,l,r);
        else Reverse(ls,L,mid,l,mid),Reverse(rs,mid+1,R,mid+1,r);
        maxx[rt]=max(maxx[rt<<1],maxx[rt<<1|1]);
        minn[rt]=min(minn[rt<<1],minn[rt<<1|1]);
        sum[rt]=sum[rt<<1]+sum[rt<<1|1]; //维护三种值
    }
    
    ll query_sum(ll rt,ll l,ll r,ll x,ll y){
        if((x>r)||(y<l)) return 0; //与询问区间无交集 
        if((x<=l)&&(r<=y)) return maxx[rt]; ll mid=(l+r)>>1; PushDown(rt);
        return (query_sum(rt<<1,l,mid,x,y)+query_sum(rt<<1|1,mid+1,r,x,y));
    }
    
    ll query_max(ll rt,ll l,ll r,ll x,ll y){
        if((x>r)||(y<l)) return 0; //与询问区间无交集 
        if((x<=l)&&(r<=y)) return maxx[rt]; ll mid=(l+r)>>1; PushDown(rt);
        return max(query_max(rt<<1,l,mid,x,y),query_max(rt<<1|1,mid+1,r,x,y));
    }
    
    ll query_min(ll rt,ll l,ll r,ll x,ll y){
        if((x>r)||(y<l)) return 0; //与询问区间无交集 
        if((x<=l)&&(r<=y)) return minn[rt]; ll mid=(l+r)>>1; PushDown(rt);
        return min(query_min(rt<<1,l,mid,x,y),query_min(rt<<1|1,mid+1,r,x,y));
    }
    
    //--------树链剖分部分----------//
    
    void dfs1(ll u,ll fa_){ //第一遍dfs:求子树大小和重儿子
        siz[u]=1,fa[u]=fa_,dep[u]=dep[fa_]+1;
        for(ll i=head[u];i;i=e[i].nextt){
            if(e[i].ver==fa_) continue; 
            rev[e[i].ver]=e[i].w,num[i>>1]=e[i].ver;
            dfs1(e[i].ver,u),siz[u]+=siz[e[i].ver];
            if(siz[e[i].ver]>siz[son[u]]) son[u]=e[i].ver; //重儿子
        }
    }
    
    void dfs2(ll u,ll fa_){ //第二遍dfs:确定dfs序和top值
        if(son[u]){ //先走重儿子,使重链在线段树中的位置连续
            id[son[u]]=++id[0]; //节点记入线段树中
            a[id[0]]=rev[son[u]]; //记录对应的原始编号
            top[son[u]]=top[u],dfs2(son[u],u); //更新top值
        } for(ll i=head[u];i;i=e[i].nextt){
            if(top[e[i].ver]) continue; //除去u的重儿子或父亲
            id[e[i].ver]=++id[0],a[id[0]]=rev[e[i].ver]; //加入线段树
            top[e[i].ver]=e[i].ver,dfs2(e[i].ver,u); //轻边上的点
        }
    }
    
    //--------修改&查询操作部分----------//
    
    ll q_route1(ll x,ll y){ //sum
        ll ans=0; while(top[x]!=top[y]){
            if(dep[top[x]]<dep[top[y]]) swap(x,y);
            ans+=query_sum(1,1,n,id[top[x]],id[x]); x=fa[top[x]];
        } if(dep[x]>dep[y]) swap(x,y); 
        ans+=query_sum(1,1,n,id[x]+1,id[y]); return ans;
    }
    
    ll q_route2(ll x,ll y){ //max
        ll ans=0; while(top[x]!=top[y]){
            if(dep[top[x]]<dep[top[y]]) swap(x,y);
            ans=max(ans,query_max(1,1,n,id[top[x]],id[x])); x=fa[top[x]];
        } if(dep[x]>dep[y]) swap(x,y); 
        ans=max(ans,query_max(1,1,n,id[x]+1,id[y])); return ans;
    }
    
    ll q_route3(ll x,ll y){ //min
        ll ans=0; while(top[x]!=top[y]){
            if(dep[top[x]]<dep[top[y]]) swap(x,y);
            ans=min(ans,query_min(1,1,n,id[top[x]],id[x])); x=fa[top[x]];
        } if(dep[x]>dep[y]) swap(x,y); 
        ans=min(ans,query_min(1,1,n,id[x]+1,id[y])); return ans;
    }
    
    void upd_route(ll x,ll y){
        //cout<<x<<" "<<y<<" : "<<id[x]+1<<" "<<id[y]<<endl;
        while(top[x]!=top[y]){
            if(dep[top[x]]<dep[top[y]]) swap(x,y);
            Reverse(1,1,n,id[top[x]],id[x]); x=fa[top[x]];
        } if(dep[x]>dep[y]) swap(x,y); 
        if(x!=y) Reverse(1,1,n,id[x]+1,id[y]);
    }
    
    //--------主程序部分----------//
    
    int main(){
        reads(n); for(ll i=1,u,v,w;i<n;i++) 
            reads(u),u++,reads(v),v++,reads(w),add(u,v,w),add(v,u,w);
        dfs1(1,0),top[1]=rev[1]=id[1]=id[0]=1;
        dfs2(1,0),build(1,1,n); reads(m); char op[19];
        while(m--){ cin>>op; ll u,v; reads(u),reads(v); u++,v++;
            if(op[0]=='C') update(1,1,n,id[num[u-1]],v-1); //单点修改
            if(op[0]=='N') upd_route(u,v); //路径变为相反数
            if(op[0]=='S') printf("%lld
    ",q_route1(u,v)); //sum
            if(op[1]=='A') printf("%lld
    ",q_route2(u,v)); //max
            if(op[1]=='I') printf("%lld
    ",q_route3(u,v)); //min
        }
    }
    p1505 旅游(err) //边权:单点/路径修改 + 路径min/max/sum

    这题真的超级复杂、细节超级多、超级容易出错......然后本蒟蒻还没AC qaq......

    { PART.3 全体标记系列 }

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<string>
    #include<queue>
    #include<vector>
    #include<cmath>
    #include<map>
    using namespace std;
    typedef long long ll;
    
    /*【p2146】软件包管理器 
    服务器的依赖关系是一棵树。根节点为0号软件。
    每次安装软件,就把根节点到x软件路径上的值全部变为1。
    每次卸载软件,就把x以及它的子树的值变为0。
    求每次操作影响了的软件总数。 */
    
    //用区间和的思想,每次操作之前记录一下sum[root]的值,
    //更新之后再查询一遍sum[root]的值,两者之差的绝对值则为答案。
    
    void reads(ll &x){ //读入优化(正负整数)
        ll fa=1;x=0;char s=getchar();
        while(s<'0'||s>'9'){if(s=='-')fa=-1;s=getchar();}
        while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
        x*=fa; //正负号
    }
    
    const ll N=1000019;
    
    ll n,m,a[N*2],tot=0,head[N*2];
    
    ll siz[N*2],son[N*2],top[N*2],dep[N*2],fa[N*2];
    
    ll rev[N*2],seg[N*4],sum[N*4],lazy[N*4];
    
    struct node{ ll nextt,ver,w; }e[N*2];
    
    void add(ll x,ll y)
     { e[++tot].ver=y,e[tot].nextt=head[x],head[x]=tot; }
    
    //--------线段树部分----------//
    
    void build(ll rt,ll l,ll r){
        lazy[rt]=-1,sum[rt]=0; if(l==r) return; //叶子节点
        ll mid=(l+r)>>1; build(rt<<1,l,mid),build(rt<<1|1,mid+1,r); }
    
    void PushDown(ll rt,ll l,ll r){ //标记下移
        ll mid=(l+r)>>1; sum[rt<<1]=lazy[rt]*(mid-l+1);
        sum[rt<<1|1]=lazy[rt]*(r-mid);
        lazy[rt<<1]=lazy[rt<<1|1]=lazy[rt],lazy[rt]=-1; }
    
    void update(ll rt,ll l,ll r,ll v,ll x,ll y){ //区间修改
        if((x>r)||(y<l)) return; //不相交区间
        if((x<=l)&&(r<=y)) //此区间完全被询问区间包含
         { sum[rt]=v*(r-l+1),lazy[rt]=v; return; }
        ll mid=(l+r)>>1; if(lazy[rt]!=-1) PushDown(rt,l,r);
        update(rt<<1,l,mid,v,x,y),update(rt<<1|1,mid+1,r,v,x,y);
        sum[rt]=sum[rt<<1]+sum[rt<<1|1];
    }
    
    //--------树链剖分部分----------//
    
    void dfs1(ll u,ll fa_){ //第一遍dfs:求子树大小和重儿子
        siz[u]=1,fa[u]=fa_,dep[u]=dep[fa_]+1;
        for(ll i=head[u];i;i=e[i].nextt){
            if(e[i].ver==fa_) continue;
            dfs1(e[i].ver,u),siz[u]+=siz[e[i].ver]; //计算size
            if(siz[e[i].ver]>siz[son[u]]) son[u]=e[i].ver; //重儿子
        }
    }
    
    void dfs2(ll u,ll fa_){ //第二遍dfs:确定dfs序和top值
        if(son[u]){ //先走重儿子,使重链在线段树中的位置连续
            seg[son[u]]=++seg[0]; //节点记入线段树中
            rev[seg[0]]=son[u]; //记录对应的原始编号
            top[son[u]]=top[u],dfs2(son[u],u); //更新top值
        } for(ll i=head[u];i;i=e[i].nextt){
            if(top[e[i].ver]) continue; //除去u的重儿子或父亲
            seg[e[i].ver]=++seg[0],rev[seg[0]]=e[i].ver; //加入线段树
            top[e[i].ver]=e[i].ver,dfs2(e[i].ver,u); //轻边上的点
        }
    }
    
    void change(ll x,ll y,ll v){
        while(top[x]!=top[y]){ //↓↓选择深度较大的
            if(dep[top[x]]<dep[top[y]]) swap(x,y); 
            update(1,1,seg[0],v,seg[top[x]],seg[x]);
            x=fa[top[x]]; //往上跳、并更新此点的top值
        } if(dep[x]>dep[y]) swap(x,y); //x、y已在同一条重链上 
        update(1,1,seg[0],v,seg[x],seg[y]);
    }
    
    //--------主程序部分----------//
    
    int main(){ //↓↓把1号节点设为根节点
        reads(n); for(ll i=2,x;i<=n;i++) reads(x),add(x+1,i);
        dfs1(1,0),seg[0]=seg[1]=top[1]=rev[1]=1; //设1为根结点
        dfs2(1,0); reads(m); char op[19]; build(1,1,seg[0]);
        for(ll i=1,lastt,x;i<=m;i++){ 
            cin>>op; reads(x); x++; lastt=sum[1];
            if(op[0]=='i') change(1,x,1),printf("%lld
    ",abs(sum[1]-lastt));
            if(op[0]=='u') update(1,1,seg[0],0,seg[x],seg[x]+siz[x]-1),
                printf("%lld
    ",abs(sum[1]-lastt));
        }
    }
    【p2146】软件包管理器
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<string>
    #include<queue>
    #include<vector>
    #include<cmath>
    #include<map>
    using namespace std;
    typedef long long ll;
    
    /*【p4116】Qtree3 //单点修改(黑白) + 求1~v上第一个黑点 */
    
    void reads(ll &x){ //读入优化(正负整数)
        ll fa=1;x=0;char s=getchar();
        while(s<'0'||s>'9'){if(s=='-')fa=-1;s=getchar();}
        while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
        x*=fa; //正负号
    }
    
    const ll N=1000019;
    
    ll n,m,a[N*2],tot=0,head[N*2],rev[N*2],id[N*4];
    
    ll siz[N*2],son[N*2],top[N*2],dep[N*2],fa[N*2];
    
    struct node{ ll nextt,ver,w; }e[N*2];
    
    void add(ll x,ll y)
     { e[++tot].ver=y,e[tot].nextt=head[x],head[x]=tot; }
    
    //--------线段树部分----------//
    
    #define lc rt<<1
    #define rc rt<<1|1
    
    struct segment{ int v;bool f; segment(){v=-1;} }t[N*4];
    
    inline void pushup(int rt){
        t[rt].f=t[lc].f|t[rc].f; //子区间中有黑点,当前区间有黑点
        t[rt].v=t[lc].f?t[lc].v:(t[rc].f?t[rc].v:-1); 
        //↑↑优先取左子区间的黑点,使距离根节点尽可能近
    }
    
    void update(int rt,int l,int r,int p){
        if(l==r){ t[rt].f^=1; t[rt].v=t[rt].f?rev[l]:-1; return;}
        int m=l+r>>1; if(p<=m) update(lc,l,m,p);
        else update(rc,m+1,r,p); pushup(rt);
    }
    int query(int rt,int l,int r,int L,int R){
        if(l>R||r<L) return -1; if(L<=l&&r<=R) return t[rt].v;
        int m=l+r>>1,l1=query(lc,l,m,L,R),r1=query(rc,m+1,r,L,R);
        return l1==-1?r1:l1; //优先取左子区间
    }
    
    //--------树链剖分部分----------//
    
    void dfs1(ll u,ll fa_){ //第一遍dfs:求子树大小和重儿子
        siz[u]=1,fa[u]=fa_,dep[u]=dep[fa_]+1;
        for(ll i=head[u];i;i=e[i].nextt){
            if(e[i].ver==fa_) continue;
            dfs1(e[i].ver,u),siz[u]+=siz[e[i].ver]; //计算size
            if(siz[e[i].ver]>siz[son[u]]) son[u]=e[i].ver; //重儿子
        }
    }
    
    void dfs2(ll u,ll fa_){ //第二遍dfs:确定dfs序和top值
        if(son[u]){ //先走重儿子,使重链在线段树中的位置连续
            id[son[u]]=++id[0]; //节点记入线段树中
            rev[id[0]]=son[u]; //记录对应的原始编号
            top[son[u]]=top[u],dfs2(son[u],u); //更新top值
        } for(ll i=head[u];i;i=e[i].nextt){
            if(top[e[i].ver]) continue; //除去u的重儿子或父亲
            id[e[i].ver]=++id[0],rev[id[0]]=e[i].ver; //加入线段树
            top[e[i].ver]=e[i].ver,dfs2(e[i].ver,u); //轻边上的点
        }
    }
    
    void solve(ll x){
        ll ans=-1,rt; 
        while(top[x]!=1){
            rt=query(1,1,n,id[top[x]],id[x]);
            ans=(rt==-1?ans:rt); x=fa[top[x]];
        } rt=query(1,1,n,1,id[x]),cout<<(rt==-1?ans:rt)<<endl;
    }
    
    //--------主程序部分----------//
    
    int main(){ 
        ll x,y; reads(n),reads(m); //初始全为白点
        for(ll i=1;i<n;i++) reads(x),reads(y),add(x,y),add(y,x);
        dfs1(1,0),id[0]=id[1]=top[1]=rev[1]=1,dfs2(1,0); 
        for(ll i=1,op,x;i<=m;i++){ cin>>op; reads(x);
            if(op==0) update(1,1,n,id[x]); //单点换色
            if(op==1) solve(x); //求1~v上第一个黑点
        }
    }
    【p4116】Qtree3

    单点修改(黑白) + 求1~v上第一个黑点......这题的方案还是比较神奇的qwq

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<string>
    #include<queue>
    #include<vector>
    #include<cmath>
    #include<map>
    using namespace std;
    typedef long long ll;
    
    /*【p4092】树 //单点标记 + 询问离v最近的一个有标记的祖先 */
    
    //线段树维护每一段区间中被标记的最深的节点。
    
    //查询时,在链上往上跳,只要找到了有标记的节点就输出。
    
    void reads(ll &x){ //读入优化(正负整数)
        ll fa=1;x=0;char s=getchar();
        while(s<'0'||s>'9'){if(s=='-')fa=-1;s=getchar();}
        while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
        x*=fa; //正负号
    }
    
    const ll N=1000019;
    
    ll n,m,a[N*2],tot=0,head[N*2],rev[N*2],id[N*4];
    
    ll siz[N*2],son[N*2],top[N*2],dep[N*2],fa[N*2];
    
    struct node{ ll nextt,ver,w; }e[N*2];
    
    void add(ll rt,ll y)
     { e[++tot].ver=y,e[tot].nextt=head[rt],head[rt]=tot; }
    
    //--------线段树部分----------//
    
    struct Tree{ int left,right,deepest; }tree[800019];
    
    void build(ll rt,ll l,ll r)
     {  tree[rt].left=l; tree[rt].right=r;tree[rt].deepest=-1; 
        if(r-l>1) build(rt*2,l,(l+r)/2),build(rt*2+1,(l+r)/2,r); }
    
    void update(int x,int l,int r){
        if(l<=tree[x].left&&r>=tree[x].right)
         { tree[x].deepest=l; return; } //只有一个元素
        int mid=(tree[x].left+tree[x].right)/2;
        if(l<mid) update(x*2,l,r); if(r>mid) update(x*2+1,l,r);
        tree[x].deepest=max(tree[x*2].deepest,tree[x*2+1].deepest); 
    }
    
    int query(int x,int l,int r){
        if(l<=tree[x].left&&r>=tree[x].right) return tree[x].deepest;
        int mid=(tree[x].left+tree[x].right)/2,ans=-1;
        if(l<mid) ans=max(ans,query(x*2,l,r));
        if(r>mid) ans=max(ans,query(x*2+1,l,r)); return ans;
    }
    
    //--------树链剖分部分----------//
    
    void dfs1(ll u,ll fa_){ //第一遍dfs:求子树大小和重儿子
        siz[u]=1,fa[u]=fa_,dep[u]=dep[fa_]+1;
        for(ll i=head[u];i;i=e[i].nextt){
            if(e[i].ver==fa_) continue;
            dfs1(e[i].ver,u),siz[u]+=siz[e[i].ver]; //计算size
            if(siz[e[i].ver]>siz[son[u]]) son[u]=e[i].ver; //重儿子
        }
    }
    
    void dfs2(ll u,ll fa_){ //第二遍dfs:确定dfs序和top值
        if(son[u]){ //先走重儿子,使重链在线段树中的位置连续
            id[son[u]]=++id[0]; //节点记入线段树中
            rev[id[0]]=son[u]; //记录对应的原始编号
            top[son[u]]=top[u],dfs2(son[u],u); //更新top值
        } for(ll i=head[u];i;i=e[i].nextt){
            if(top[e[i].ver]) continue; //除去u的重儿子或父亲
            id[e[i].ver]=++id[0],rev[id[0]]=e[i].ver; //加入线段树
            top[e[i].ver]=e[i].ver,dfs2(e[i].ver,u); //轻边上的点
        }
    }
    
    ll q_ans(ll u,ll v){
        ll ans=-1; while(top[u]!=top[v]){
            if(dep[id[u]]<dep[id[v]]) swap(u,v);
            ans=query(1,id[top[u]],id[u]+1);
            if(ans!=-1) return rev[ans]; u=fa[top[u]];
        } if(dep[u]>dep[v]) swap(u,v);
        ans=query(1,id[u],id[v]+1); return rev[ans];
    }
    
    //--------主程序部分----------//
    
    int main(){ 
        ll x,y; char op[19]; reads(n),reads(m); //初始全未标记
        for(ll i=1;i<n;i++) reads(x),reads(y),add(x,y),add(y,x);
        dfs1(1,0),id[0]=id[1]=top[1]=rev[1]=1,dfs2(1,0);
        build(1,1,n+1),update(1,1,2); //预先给根节点打标记 
        for(ll i=1;i<=m;i++){ cin>>op; reads(x);
            if(op[0]=='C') update(1,id[x],id[x]+1);
            if(op[0]=='Q') printf("%lld
    ",q_ans(x,1));
        }
    }
    【p4092】树
    • 单点标记 + 询问离v最近、有标记的祖先:线段树维护区间中被标记的最深节点。
    • 查询时,在链上往上跳,只要找到了有标记的节点就输出。

                                  ——时间划过风的轨迹,那个少年,还在等你

  • 相关阅读:
    linux查看cpu、内存信息
    PHP之路,Day1
    Zabbix3.0完整部署
    linux时间同步
    nginx日志切割脚本
    Rsync+sersync文件实时同步
    阿里云自动挂载云盘脚本
    nginx不支持pathinfo 导致thinkphp出错解决办法
    VIM选项配置说明
    vagrant 本地添加box 支持带版本号
  • 原文地址:https://www.cnblogs.com/FloraLOVERyuuji/p/10409559.html
Copyright © 2020-2023  润新知