树链剖分详解及其模板题

7 篇文章 0 订阅
6 篇文章 0 订阅

详解

转自foreverpiano的安心小窝~

1 前言

如果给你一棵树,求点u到点v路径上点的权值之和,你可能会说:倍增啊!

那如果出题人:我还要你支持修改某个点的权值!

或者再j一点:我还要你支持修改点u到点v路径上点的权值!

那就得用树链剖分了。

2 什么是树链剖分

上面那个问题,树上区间修改。 区间修改最常见做法就是线段树了。

那我们怎么用线段树维护一颗。。。普通的树呢?

那就给普通的树的每个节点标个号,然后放线段树里呗。区间维护。

但如果随便标号,那点u到点v路径不一定标号是连续的啊,你线段树维护个j啊
所以我们现在引入一个(堆)姿势:

重(zhong)儿子:siz[u]为v的子节点中siz值最大的,那么u就是v的重儿子。
轻儿子:v的其它子节点。
重边:点v与其重儿子的连边。
轻边:点v与其轻儿子的连边。
重链:由连续的重边连成的一条链。
轻链:由连续的轻边组成的一条链。

我们先来看张图:

这里写图片描述
图中标在边上了,但也不影响我们学习。。。
标号方法是:
跑dfs,先给当前节点标号,

再给重儿子标号(重儿子和当前节点在一个重链上),

然后对重儿子递归,

最后给剩下的别的儿子标号(别的儿子不和当前节点在一个重链上,所以新建
重链,把新建的重链的顶端节点设为那个”别的儿子“)、

递归(图中是先给重(zhong)边标号,再给剩下的边标号)。标号从小到大。

不难发现一条重链上的标号是连续的,比如点1到点14,点2到点12

这意味着在线段树中,它们是在一个连续的区间里的,而不是像随便标号时断断续续的。

这样就很好用线段树处理了。

如果两个节点不在一条重链上呢?

比如图中的点11和点10,我们要求它们之间的路径上的点权和

那我们就看,点11所在的重链是11->6->2,点10所在的重链是10(10所在的重链只有一个点,就是10)。所以我们就先求出重链11->6->2上的点权和、重链10上的点权和。这两条重链在线段树上都是一段连续的区间,可以直接log2n求出
这时候我们发现还有4->1的重链没有计算,就把它的点权和计算出来,三个重链的点权和加在一起就得到了答案。
所以我们要记录的是:
1. pos[i] 点i的标号
2. top[i] 点i所在重链的顶端节点
3. siz[i] 以点i为根的子树的大小
4. dep[i] 点i的深度
5. fa[i] 点i的父亲节点
6. son[]数组,用来保存重儿子

具体步骤:

我们先跑一边dfs,算出fa、size、dep
然后再跑一边dfs,根据size[i]找出点i的重儿子,然后算出pos、top。
搞完这些就很easy了,因为一段重链在线段树里是一段连续的区间(这是坠重要的)。

我们在查询/修改从点u到点v的路径时,
先找到所在重链的顶端节点(top)深度较深的
(因为这样能让u和v同步提升,防止一个提到根节点了,另一个没提,这时你就不知道提谁了)
注意不能按照u和v的深度来提!

比如top较深的点是u,然后就用线段树处理区间(因为top[u]的标号一定比u要小),再设置u为fa[top[u]],把u往上提,直至u和v在一条重链上(即top[u]==top[v])

这时候可能u和v之间还有一段距离,此时u和v已经在一条重链上,直接处理它们之间的区间就行了。

然后复杂度就是:这里写图片描述
同时这个复杂度也是一般的树链剖分的复杂度。因为重链个数不会超过个,线段树复杂度是的。网上有证明,我就不做过多赘述”.

例题

洛谷 P3384 【模板】树链剖分

此代码是用线段树维护的,这道题主要是维护树中的点权。

#include<bits/stdc++.h>
#define ls (rt<<1)
#define rs (rt<<1|1)
#define mid (tr[rt].l+(tr[rt].r-tr[rt].l)/2)
#define maxn 600000
#define ll long long
using  namespace std;
//1. pos[i] 点i的标号
//2. top[i] 点i所在重链的顶端节点
//3. siz[i] 以点i为根的子树的大小
//4. dep[i] 点i的深度
//5. fa[i] 点i的父亲节点
//son[]数组,用来保存重儿子
struct TREE{ll l,r,sum,tag;}tr[maxn<<2];
ll n,m,rr,mod;
int u,v,pos[maxn],sz[maxn],top[maxn],son[maxn],
fa[maxn],ww[maxn],w[maxn],id[maxn],deep[maxn],cnt=0;
vector<int> g[maxn];//动态数组 

template<typename tp>void read(tp & dig){//读入优化 
    char c=getchar();dig=0;
    while(!isdigit(c))c=getchar();
    while(isdigit(c))dig=dig*10+c-'0',c=getchar();
}
inline void pushup(int rt){tr[rt].sum=tr[ls].sum+tr[rs].sum;}
inline void build(int l,int r,int rt){//建立一棵线段树 
    tr[rt].l=l,tr[rt].r=r;
    if(l==r){tr[rt].sum=w[id[l]];return ;}
    int midd=l+(r-l)/2;
    build(l,midd,ls),build(midd+1,r,rs);
    pushup(rt);
}
inline void pushdown(int rt){//下传标记 
    if(tr[rt].tag){
        tr[ls].tag=(tr[ls].tag+tr[rt].tag)%mod;
        tr[rs].tag=(tr[rs].tag+tr[rt].tag)%mod;
        tr[ls].sum=(tr[ls].sum+(tr[ls].r-tr[ls].l+1)*tr[rt].tag)%mod;
        tr[rs].sum=(tr[rs].sum+(tr[rs].r-tr[rs].l+1)*tr[rt].tag)%mod;
        tr[rt].tag=0;
    }
}

inline void update(int l,int r,int c,int rt){//区间、单点修改 
    if(l<=tr[rt].l&&tr[rt].r<=r){   
        tr[rt].sum=(tr[rt].sum+(c*(tr[rt].r-tr[rt].l+1)%mod))%mod;
        tr[rt].tag+=c%mod;
        return ;
    }
    pushdown(rt);
    if(l<=mid) update(l,r,c,ls);
    if(r>mid) update(l,r,c,rs);
    pushup(rt);
}

inline ll query(int l,int r,int rt){//区间查询 
    if(l<=tr[rt].l&&tr[rt].r<=r) return tr[rt].sum;
    pushdown(rt);
    int ans=0;
    if(l<=mid) ans+=query(l,r,ls),ans%=mod;//if(r>mid) ans+=query(l,r,rs),ans%=mod;
    return ans%mod;
}

inline void dfs1(int x,int fat,int dep) {
    //这里应该就是树链剖分的第一个操作,把树上每个节点的size,fa等统统搞出来
    deep[x]=dep,fa[x]=fat,sz[x]=1;//fat表示x的爸爸 
    for(int i=0;i<g[x].size();i++){
        int v=g[x][i];
        if(v!=fat){
            dfs1(v,x,dep+1);
            sz[x]+=sz[v];
            if(son[x]==-1||sz[son[x]]<sz[v]) son[x]=v;//son[x]记录x的儿子中的重点 
        }
    }
}

inline void dfs2(int x,int tp){//这个操作应该就是处理轻链和重链 
    top[x]=tp;pos[x]=++cnt;id[pos[x]]=x;//pos记录的是遍历到该点时的时间,id记录的是当时间为pos[x]时的编号是xif(son[x]==-1) return ;//到了叶子节点就退出。 
    dfs2(son[x],tp);//继续递归,并且是处理的重链和重点 
    for(int i=0;i<g[x].size();i++){
        int v=g[x][i];
        if(v!=fa[x]&&v!=son[x]) dfs2(v,v);//处理轻链 
    }//tp记录的是重点的祖先节点编号 
}

inline ll add(int t1,int t2,int c,int ok){//ok为一个标记,表示是进行哪一个操作 
    ll u=t1,v=t2,ans=0;
    while(top[u]!=top[v]){//在不同链上的情况 
        if(deep[top[u]]>deep[top[v]]) swap(u,v); 
        if(!ok)update(pos[top[v]],pos[v],c,1);//这里是更新值,例如本题中的从a到b都加上某一个值 
        else ans+=query(pos[top[v]],pos[v],1),ans%=mod;
        v=fa[top[v]];//继续操作下去,直到这两个点爬到他们的公共节点 
    }
    if(deep[u]>deep[v]) swap(u,v);//处理在同一条链上的情况 
    if(!ok) update(pos[u],pos[v],c,1);
    else ans+=query(pos[u],pos[v],1);
    return ans%=mod;
}
int main(){
    memset(son,-1,sizeof(son));
    read(n),read(m),read(rr),read(mod);//rr为根节点 
    for(int i=1;i<=n;i++) read(w[i]);
    for(int i=1;i<n;i++)
        read(u),read(v),g[u].push_back(v),g[v].push_back(u);
    dfs1(rr,-1,1),
    dfs2(rr,rr);//important
    build(1,n,1);
    for(int i=1;i<=m;i++){
        int xx,t1,t2,t3;
        cin>>xx;
        if(xx==1) read(t1),read(t2),read(t3),add(t1,t2,t3,0);
        if(xx==3) read(t1),read(t2),update(pos[t1],pos[t1]+sz[t1]-1,t2,1);
        if(xx==2) read(t1),read(t2),printf("%lld\n",add(t1,t2,0,1)%mod);
        if(xx==4) read(t2),printf("%lld\n",query(pos[t2],pos[t2]+sz[t2]-1,1)%mod);
    }
    return 0;
}
/*
5 5 2 24
7 3 7 8 0 
1 2
1 5
3 1
4 1
3 4 2
3 2 2
4 5
1 5 1 3
2 1 3
*/ 

以上代码摘自http://www.cnblogs.com/foreverpiano/p/7142189.html

本题的另一种写法:(本校的一位大佬用树状数组来维护的,在洛谷上跑的飞快)

#include<cstdio>
#define re register int
char ss[1<<17],*A=ss,*B=ss;
inline char gc(){if(A==B){B=(A=ss)+fread(ss,1,1<<17,stdin);if(A==B)return EOF;}return*A++;}
template<class T>inline void sdf(T&x){
    char c;re y=1;while(c=gc(),c<48||57<c)if(c=='-')y=-1;x=c^48;
    while(c=gc(),47<c&&c<58)x=(x<<1)+(x<<3)+(c^48);x*=y;
}
char sr[1<<20],z[20];int C=-1,Z;
template<class T>inline void wer(T x){
    re y=0;if(x<0)y=1,x=-x;
    while(z[++Z]=x%10+'0',x/=10);if(y)z[++Z]='-';
    while(sr[++C]=z[Z],--Z);sr[++C]='\n';
}
const int N=5e5+5;
typedef int array[N];
typedef long long ll;
struct edges{int nx,to;}e[N<<1];
int n,m,s,mod,tot,tmp;array a,b,depth,fa,son,size,top,id,real,fi;ll c1[N],c2[N];
void dfs1(re u){
    depth[u]=depth[fa[u]]+(size[u]=1);
    for(re i=fi[u],v;i;i=e[i].nx)
        if((v=e[i].to)!=fa[u]){
            fa[v]=u;dfs1(v);size[u]+=size[v];
            if(size[v]>size[son[u]])son[u]=v;
        }
}
void dfs2(re u){
    if(son[fa[u]]==u)top[u]=top[fa[u]];
    else top[u]=u;
    real[id[u]=++tmp]=u;
    if(son[u])dfs2(son[u]);
    for(re v,i=fi[u];i;i=e[i].nx)
        if((v=e[i].to)!=fa[u]&&v!=son[u])
            dfs2(v);
}
inline void insert(re x,ll w){for(re i=x;i<=n;i+=i&(-i))c1[i]+=w,c2[i]+=(ll)x*w;}
inline ll sigma(re x){ll sum=0;for(re i=x;i;i-=i&(-i))sum+=(ll)(x+1)*c1[i]-c2[i];return sum;}
inline void swap(re&x,re&y){re t=x;x=y;y=t;}
inline void change(re u,re v,re w){
    while(top[u]!=top[v]){
        if(depth[top[u]]<depth[top[v]])swap(u,v);
        insert(id[top[u]],w);insert(id[u]+1,-w);
        u=fa[top[u]];
    }
    if(depth[u]>depth[v])swap(u,v);
    insert(id[u],w);insert(id[v]+1,-w);
}
inline ll sum(re u,re v){
    ll sum=0;
    while(top[u]!=top[v]){
        if(depth[top[u]]<depth[top[v]])swap(u,v);
        sum+=sigma(id[u])-sigma(id[top[u]]-1);
        u=fa[top[u]];
    }
    if(depth[u]>depth[v])swap(u,v);
    sum+=sigma(id[v])-sigma(id[u]-1);
    return sum%mod;
}
inline void add(re u,re v){e[++tot]=(edges){fi[u],v};fi[u]=tot;}
int main(){
    sdf(n);sdf(m);sdf(s);sdf(mod);
    for(re i=1;i<=n;++i)sdf(a[i]);
    for(re u,v,i=1;i<n;++i)sdf(u),sdf(v),add(u,v),add(v,u);
    dfs1(s);dfs2(s);
    for(re i=1;i<=n;i++)b[i]=a[real[i]];
    for(re i=1;i<=n;i++)insert(i,b[i]-b[i-1]);
    re op,x,y,z;
    while(m--){
        sdf(op);sdf(x);
        if(op==1)sdf(y),sdf(z),change(x,y,z);
        else if(op==2)sdf(y),wer(sum(x,y));
        else if(op==3)sdf(y),insert(id[x],y),insert(id[x]+size[x]-1+1,-y);
        else wer((sigma(id[x]+size[x]-1)-sigma(id[x]-1))%mod);
    }
    fwrite(sr,1,C+1,stdout);
return 0;
}

另外几篇博客:
树链剖分
树链剖分基础模板
树链剖分模板题

poj 3237 tree

【题目大意】
第一个操作:将第i条边的权值变为v。
第二个操作:将点a到点b路径上的边权变为其相反数。
第三个操作:查询点a到点b路径上的边权最大值。
【题目解析】
这道题是一道裸的熟练剖分的题,和上面那一道题不同的是,这道题修改和查询的值为边的权值。所以要处理一些细节,将在下面代码中解释。

/*
ls:线段树组中p节点左儿子的编号,相当于p<<1(在代码中有很多地方要用到,懒的打)
rs:右儿子
mid:当前在线段树中的区间的中点
deep:每个节点在树中的深度
sz:以该点i为根的树的大小
fa:点i的父亲节点
road:第i条边所指向的点
pos:第二个dfs中当前节点遍历到的时间
a:父亲节点到当前儿子的边权值
son_cost:当前节点与重儿子连的边的权值
top:点i所在重链能到的最顶上的节点
son:记录节点i的重儿子
tree:线段树组
mx:当前区间的最大边权值
mi:当前区间的最小边权值
fg:相当于线段树中的tag
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#define ls p<<1
#define rs (p<<1)+1
#define mid (tree[p].l+(tree[p].r-tree[p].l)/2)
using namespace std;
struct Node{
    int l,r,p,sum;
}tree[500005];
struct arr{
    int nd,nx,co,id;
}edge[100005];//这个地方既可以用邻接链表储存,也可以用动态数组来存 
int head[100005],deep[100005],pos[100005],son_cost[100005];
int top[100005],fa[100005],sz[100005],son[100005],road[100005],a[100005];
int t,n,cnt,tot,ans;
int mx[500005],mi[500005],fg[500005];
inline void add(int u,int v,int w,int id){ edge[++cnt].nd=v; edge[cnt].co=w; edge[cnt].id=id;edge[cnt].nx=head[u]; head[u]=cnt; }
inline int read(){//读入优化
    int x=0,w=1;char ch=0;
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-') w=-1,ch=getchar();
    while(ch>='0'&&ch<='9') x=x*10+ch-48,ch=getchar();
    return x*w;
}

void dfs1(int u,int father,int depth){//建立起一棵树,并且查找出重儿子,节点深度,节点的大小
    deep[u]=depth;fa[u]=father;sz[u]=1;
    for(int i=head[u];i;i=edge[i].nx){
        int v=edge[i].nd;
        if(v!=father){
            road[edge[i].id]=v;
            dfs1(v,u,depth+1);
            sz[u]+=sz[v];
            if(son[u]==-1||sz[son[u]]<sz[v]) {
                son[u]=v;
                son_cost[u]=edge[i].co;
            }//第二个if是为了记录重儿子是哪一个 ,并且记录它与重儿子的边权值
        }
    }
}
void dfs2(int u,int tp,int cost){//这个操作是将重轻链的最顶端的点求出来 
    top[u]=tp;pos[u]=++tot;a[tot]=cost;//a的作用是把每个边权值记录下来,方便放入线段树中
    if(son[u]==-1) return;
    dfs2(son[u],tp,son_cost[u]);
    for(int i=head[u];i;i=edge[i].nx){
        int v=edge[i].nd;
        if(v!=fa[u]&&v!=son[u]) dfs2(v,v,edge[i].co);
    }
} 

inline void pushup(int p){  mx[p]=max(mx[ls],mx[rs]); mi[p]=min(mi[ls],mi[rs]);  }//线段树中的上传操作
inline void push_down(int p){//下传标记
    if(fg[p]){
        fg[ls]^=1; fg[rs]^=1;
        int tmp=mx[ls]; mx[ls]=-mi[ls]; mi[ls]=-tmp;
        tmp=mx[rs]; mx[rs]= -mi[rs]; mi[rs]= -tmp; 
        fg[p]=0;
    }//用mi数组的原因是,此题在做取相反数操作的时候,会变成负数,方便取最大值
}
void build(int l,int r,int p){//建树
    tree[p].l=l; tree[p].r=r;
    if(l==r) {
        mx[p]=a[l];
        mi[p]=a[l];//边权
        return;
    }
    int midd=(l+r)>>1;
    build(l,midd,ls);
    build(midd+1,r,rs);
    pushup(p);
}
void change(int p,int l,int data){//把某条边的权值改掉
    if (tree[p].l==tree[p].r){
        mi[p]=mx[p]=data;
    } else {
        push_down(p);
        if (l<=mid) change(ls,l,data);
        else change(rs,l,data);
        pushup(p);
    }
}
void nega(int p,int l,int r){//取相反数操作
    if(l<=tree[p].l&&tree[p].r<=r){
        fg[p]^=1; 
        int tmp=mx[p];
        mx[p]=-mi[p];
        mi[p]=-tmp;
        return;
    }
    push_down(p);
    if(l<=mid) nega(ls,l,r);
    if(r>mid) nega(rs,l,r);
    pushup(p);
}
void NEGATE(int x,int y,int n){//树链剖分中的必要操作
    while(top[x]!=top[y]){
        if(deep[top[x]]<deep[top[y]]) swap(x,y);
        nega(1,pos[top[x]],pos[x]);
        x=fa[top[x]];
    }
    if(x==y) return;
    if(deep[x]>deep[y]) swap(x,y);
    nega(1,pos[son[x]],pos[y]);
}

int query(int p,int l,int r){
    if(l<=tree[p].l&&tree[p].r<=r) return mx[p];
    push_down(p);
    int ans = -(1<<30);
    if(l<=mid) ans=max(ans,query(ls,l,r));
    if(r>mid) ans=max(ans,query(rs,l,r));
    pushup(p);
    return ans;
}

int QUERY(int x,int y,int n){
//树链剖分的必要操作,和上面的NEGATE差不多,只是适应不同需要
    if(x==y) return 0;
    int ans=-(1<<30);
    while(top[x]!=top[y]){
        if(deep[top[x]]<deep[top[y]]) swap(x,y);
        ans=max(ans,query(1,pos[top[x]],pos[x]));
        x=fa[top[x]];
    }
    if(x==y) return ans;
    if(deep[x]>deep[y]) swap(x,y);
    ans=max(ans,query(1,pos[son[x]],pos[y]));
    return ans;
} 
void init(){//初始化
    tot=cnt=0;
    memset(head,0,sizeof(head));
    memset(son,-1,sizeof(head));
    memset(fg,0,sizeof(fg));
}
int main(){
    t=read();
    for(int k=1;k<=t;k++){
        n=read();init();
        for(int i=1;i<=n-1;i++){
            int u=read(),v=read(),w=read(); 
            add(u,v,w,i);add(v,u,w,i);
        }
        dfs1(1,0,0); 
        dfs2(1,1,0); 
        build(2,n,1);
        char ch[10];
        while(~scanf("%s",ch)){
            if(*ch=='D') break;
            int u=read(),v=read();
            if(*ch=='Q')printf("%d\n",QUERY(u,v,n));
            else if(*ch=='C') change(1,pos[road[u]],v);
            else NEGATE(u,v,n);
        }
    }
}

以上代码摘自G_lory(部分修改过)

SPOJ QTREE - Query on a tree

题目分析

与例题1不同,t1是要维护点权,而这道题是要维护边权,一种操作是修改某条边的权值,一种操作是查询a到b最大的边权.(直接上代码吧)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define mid (tree[rt].l+(tree[rt].r-tree[rt].l)/2)
#define ls (rt<<1)
#define rs (rt<<1|1)
using namespace std;
const int MAXN=1e4+5;
struct arr{
    long long l,r,max,id;
}tree[MAXN<<2];
struct ar1{
    int nd,nx,co,id;
}bot[MAXN*2];
int n,m,cnt,tot,t;
int pos[MAXN],id[MAXN],son[MAXN],fa[MAXN],son_cost[MAXN];
int w[MAXN],deep[MAXN],top[MAXN],sz[MAXN],head[MAXN],road[MAXN];

inline int read(){
    int x=0,w=1;char ch;
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-') w=-1,ch=getchar();
    while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch-48),ch=getchar();
    return x*w;
}
inline void init(){
    cnt=0;tot=0;
    memset(head,0,sizeof(head));memset(pos,0,sizeof(pos));memset(fa,0,sizeof(fa));
    memset(id,0,sizeof(id));memset(son,0,sizeof(son));memset(son_cost,0,sizeof(son_cost));
    memset(w,0,sizeof(w));memset(deep,0,sizeof(deep));memset(top,0,sizeof(top));
    memset(sz,0,sizeof(sz));memset(road,0,sizeof(road));memset(fa,0,sizeof(fa));
    memset(bot,0,sizeof(bot));
}
inline void insert_add(int a,int b,int c,int id){ bot[++cnt].nd=b; bot[cnt].nx=head[a]; bot[cnt].co=c;bot[cnt].id=id;head[a]=cnt; }
inline void pushup(int rt) {tree[rt].max=max(tree[ls].max,tree[rs].max);}
/*inline void pushdown(int rt) {
    if(tree[rt].tag){
        tree[ls].tag=(tree[ls].tag+tree[rt].tag);
        tree[rs].tag=(tree[rs].tag+tree[rt].tag);
        tree[ls].sum=(tree[ls].sum+(tree[ls].r-tree[ls].l+1)*tree[rt].tag);
        tree[rs].sum=(tree[rs].sum+(tree[rs].r-tree[rs].l+1)*tree[rt].tag);
        tree[rt].tag=0;
    }
}*/
void build(int l,int r,int rt) {
    tree[rt].l=l;tree[rt].r=r;
    if(l==r) {tree[rt].max=w[l];tree[rt].id=id[l];return;}
    int midd=(l+(r-l)/2);
    build(l,midd,ls);build(midd+1,r,rs);
    pushup(rt);
}
void dfs1(int x,int fat,int dep) {//第一遍dfs处理出重儿子
    deep[x]=dep,fa[x]=fat,sz[x]=1;
    for(register int i=head[x];i;i=bot[i].nx) {
        int v=bot[i].nd;
        if(v!=fat) {
            road[bot[i].id]=v;
            dfs1(v,x,dep+1);
            sz[x]+=sz[v];
            if(son[x]==0||sz[son[x]]<sz[v]) {
                son[x]=v;
                son_cost[x]=bot[i].co;
            }
        }
    }
}
void dfs2(int x,int tp,int cost){
    top[x]=tp;pos[x]=++tot;id[tot]=x;w[tot]=cost;
    if(son[x]==0) return ;
    dfs2(son[x],tp,son_cost[x]);
    for(register int i=head[x];i;i=bot[i].nx) {
        int v=bot[i].nd;
        if(v!=fa[x]&&v!=son[x]) dfs2(v,v,bot[i].co);
    }
}
void updata(int rt,int l,int data) {
    if(tree[rt].l==tree[rt].r) { tree[rt].max=data; return ;}
    else {
        if(l<=mid) updata(ls,l,data);
        else updata(rs,l,data);
        pushup(rt);
    }
}
long long query(int l,int r,int rt) {
    if(l<=tree[rt].l&&tree[rt].r<=r) return tree[rt].max;
    long long ans=-0x3f3f3f3f;
    if(l<=mid) ans=max(ans,query(l,r,ls));
    if(r>mid) ans=max(ans,query(l,r,rs));
    pushup(rt);
    return ans;
}
long long Query(int t1, int t2,int data) {
    long long ans=-0x3f3f3f3f,u=t1,v=t2;
    if(t1==t2) return 0;
    while(top[u]!=top[v]) {
        if(deep[top[u]]>deep[top[v]]) swap(u,v);
        ans=max(ans,query(pos[top[v]],pos[v],1));
        v=fa[top[v]];
    }
    if(u==v) return ans;
    if(deep[u]>deep[v]) swap(u,v);
    ans=max(ans,query(pos[son[u]],pos[v],1));
    //如果在一条重链或轻链上时,不知为何,在处理点权时这样写query(pos[u],pos[v],1),
    //边权时这样写query(pos[son[u]],pos[v],1)
    return ans;
}
int main(){
    freopen("qtree.in","r",stdin);
    freopen("qtree1.out","w",stdout);
    t=read();
    for(register int k=1;k<=t;++k) {
        n=read();init();
        for(register int i=1;i<n;++i) {
            int u=read(),v=read(),w=read();
            insert_add(u,v,w,i);insert_add(v,u,w,i);
        }
        char ch[10];
        dfs1(1,1,1);
        dfs2(1,1,0);
        build(1,n,1);
        while(~scanf("%s",ch)) {
            if(ch[0]=='D') break;
            int x=read(),y=read();
            if(ch[0]=='Q') { cout<<Query(x,y,0)<<endl;}//区间查询
            if(ch[0]=='C') { updata(1,pos[road[x]],y);}
            /*单点修改,记住修改边的时候必须写成pos[road[x]]
            road[x]记录的是第x条边在原来dfs1的时候是第几条边进入的
            pos[]是记录的在dfs2中处理轻重链时是第几条,可以想对应线段树的那个区间
            */
        }
        cout<<endl;
    }
    return 0;
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值