【树链剖分模板】题

本文介绍树链剖分与线段树结合解决树上查询与更新问题的方法,包括对边和对点的操作。通过具体题目解析,如SPOJ375、hdu3966及BZOJ1036等,详细讲解了如何构建树链剖分和线段树,并实现高效的路径查询与更新。
摘要由CSDN通过智能技术生成

一、SPOJ375——Query on a tree (对边进行操作)

给一棵树 , N个点
两种操作:
1.i-ti :把第i条边的边权修改成ti
2.a-b :询问a到b路径上的边权最大值。

#include <bits/stdc++.h>
using namespace std;
 /*
 */  
const int MAXN = 20010;  
struct Edge  
{  
    int to,next;  
}edge[MAXN*2];  
int head[MAXN],tot;  

int top[MAXN];//top[v]表示v所在的重链的顶端节点  
int fa[MAXN]; //父亲节点  
int deep[MAXN];//深度  
int num[MAXN];//num[v]表示以v为根的子树的节点数  
int p[MAXN];//p[v]表示v与其父亲节点的连边在线段树中的位置  
int fp[MAXN];//和p数组相反  即 记录线段树中的某个位置的边的起点?
int son[MAXN];//重儿子  
int pos;   // 每条边在线段树中的位置。

int n;  

void init()  
{  
    tot = 0;  
    memset(head,-1,sizeof(head));  
    pos = 1;//边的序号其实是从1开始?  
    memset(son,-1,sizeof(son));  
}  
void addedge(int u,int v)  
{  
    edge[tot].to = v;edge[tot].next = head[u];head[u] = tot++;  
}  
void dfs1(int u,int pre,int d) //第一遍dfs求出fa,deep,num,son  
{  
    deep[u] = d;  
    fa[u] = pre;  
    num[u] = 1;  
    for(int i = head[u];i != -1; i = edge[i].next)  
    {  
        int v = edge[i].to;  
        //因为路径是双向的,所以不能等于父节点
        if(v == pre)  continue;   
        dfs1(v,u,d+1);  
        //更新u节点的儿子数目  
        num[u] += num[v];  
        if(son[u] == -1 || num[v] > num[son[u]])//求出重儿子即:所有儿子中size 最大的节点v
                son[u] = v;  
    }  
}  

//因为对于轻儿子来说,top[u]=u,对于重儿子来说,如果son[v]!=-1,那么top[v]=top[son[v]]  
void getpos(int u,int sp) //第二遍dfs求出top和p  
{  
    top[u] = sp;  
    //先找重儿子  
    if(son[u] != -1)   // 存在重儿子
    {  
        //把边的位置标记一下  
        p[u] = pos++;  
        //fp相当于是p的反函数,即记录线段树中某个位置的边 的在树中的起点。
        fp[p[u]] = u;  
        //更新重儿子  
        getpos(son[u],sp);  
    }  
    //如果到了叶子节点  
    else  
    {  
        //不再向下dfs  
        p[u] = pos++;  
        fp[p[u]] = u;  
        return;  
    }  
    //更新其他的节点  
    for(int i = head[u] ; i != -1; i = edge[i].next)  
    {  
        int v = edge[i].to;  
        if(v != son[u] && v != fa[u])  
            getpos(v,v);   //  轻儿子的top[u]=u
    }  
}  

#define lson l,m,rt<<1  
#define rson m+1,r,rt<<1|1  
int MAX[MAXN<<2];  
int val[MAXN<<2];  

void pushup(int rt){  
    MAX[rt]=max(MAX[rt<<1],MAX[rt<<1|1]);  
}  

void build(int l,int r,int rt){  
    if(l==r){  
        MAX[rt]=val[l];  
        return;  
    }  
    int m=(l+r)>>1;  
    build(lson);  
    build(rson);  
    pushup(rt);  
}  

void update(int p,int x,int l,int r,int rt){  // 单点更新
    if(l==r){  
        MAX[rt]=x;  
        return;  
    }  
    int m=(l+r)>>1;  
    if(p<=m)  
        update(p,x,lson);  
    else  
        update(p,x,rson);  
    pushup(rt);  
}  

int query(int L,int R,int l,int r,int rt){    // 区间询问
    if( L<=l &&r<=R){  
        return MAX[rt];  
    }  
    int m=(l+r)>>1;  
    int res=0;  
    if(m>=L)  
        res=max(res,query(L,R,lson));  
    if(R>m)  
        res=max(res,query(L,R,rson));  
    return res;  
}  

int _find(int u,int v){   // 找 a-----b 中所有路径的最大值。
    int f1=top[u],f2=top[v];//先找到两个端点的重链顶端节点,如果是轻儿子,就是它本身  
    int temp=0;  
    while(f1!=f2){  
        //从深度较深的开始查询  
        if(deep[f1]<deep[f2]){  
            swap(f1,f2);  
            swap(u,v);  
        }  
        //查询一条重链上的最大值  
        temp=max(temp,query(p[f1],p[u],1,n,1));  
        u=fa[f1];f1=top[u];  
    }  
    //如果f1=f2代表在同一条重链上m,如果u=v代表更新结束  
    if(u==v)  
        return temp;  
    if(deep[u]>deep[v])  
        swap(u,v);  
    return max(temp,query(p[son[u]],p[v],1,n,1));  
}  

int e[MAXN][3];  
int main(){  
    int t;  
    freopen("1.txt","r",stdin);
    scanf("%d",&t);  
    while(t--){  
        init();  
        scanf("%d",&n);  
        getchar();  
        for(int i=0;i<n-1;i++){  
            scanf("%d%d%d",&e[i][0],&e[i][1],&e[i][2]);  
            addedge(e[i][0],e[i][1]);  
            addedge(e[i][1],e[i][0]);  
        }  
        dfs1(1,0,0);  
        getpos(1,1);            // 现在已经把树剖分完毕。
        for(int i=0;i<n-1;i++){    
            if(deep[e[i][0]]<deep[e[i][1]])    // 调整每条路径的父亲,儿子关系。0>1 0为儿子1为父亲
                swap(e[i][0],e[i][1]);  
            val[ p[e[i][0]] ]=e[i][2];   //  pi表示i和其父亲连边在线段树中的位置。
        }  
        build(1,n,1);   // 以val建树
        char op[10];  
        int u,v;  
        while(scanf("%s",op)){  
            if(op[0]=='D') break;  
            scanf("%d%d",&u,&v);  
            if(op[0]=='Q')  
                printf("%d\n",_find(u,v));  
            else  
                update(p[e[u-1][0]],v,1,n,1);  
        }  
    }  
    return 0;  
}  

这是模板,可以发现,这个数据结构和大多数事物一样,揭开这一层神秘的面纱之后也就那么一回事。最关键的还是思维,这个很重要。

二、hdu3966(对点进行操作)

题意:给一棵树N个点,并给定各个点权的值,然后有3种操作:
I C1 C2 K: 把C1与C2的路径上的所有点权值加上K
D C1 C2 K:把C1与C2的路径上的所有点权值减去K
Q C:查询节点编号为C的权值

这个题只对点进行操作,那么我们线段树里面放点,
现在的问题是需要对某条路径上所有的点(所有的路径其实也同理)进行操作,那么该如何?
首先我们在线段树中是按重链在前,轻链在后 有顺序的放进去的(pos)
那么我们肯定也可以在线段树中区间更新(和边是同理)

例如要更新[l,r]
那么首先找到 top[l] top[r] 如果top相等 那么表示他们在一条重链上,此时其点(边)在线段树中都是连续的
如果不等,那么 我们选择deep较深的那个节点向较深的节点靠近,我们假设L为那个更深的节点. 更新(L, top[L]) 然后让L=top[L] ,

然后我们可以发现重复此过程可以把所有的点(或边更新完) 最多不会超过logn次

const int MAXN = 50010;  
struct Edge  
{  
    int to,next;  
}edge[MAXN*2];  
int head[MAXN],tot;  

int top[MAXN];//top[v]表示v所在的重链的顶端节点  
int fa[MAXN]; //父亲节点  
int deep[MAXN];//深度  
int num[MAXN];//num[v]表示以v为根的子树的节点数  
int p[MAXN];//p[v]表示点 在线段树中的位置  
int fp[MAXN];//和p数组相反  即 记录线段树中的某个位置 存的是那个点
int son[MAXN];//重儿子  
int pos;   // 每个点在线段树中的位置。

int n;  
  int  a[MAXN];
  int b[MAXN];
void init()  
{  
    tot = 0;  
    memset(head,-1,sizeof(head));  
    pos = 1;//边的序号其实是从1开始?  
    memset(son,-1,sizeof(son));  
}  
void addedge(int u,int v)  
{  
    edge[tot].to = v;edge[tot].next = head[u];head[u] = tot++;  
}  
void dfs1(int u,int pre,int d) //第一遍dfs求出fa,deep,num,son  
{  
    deep[u] = d;  
    fa[u] = pre;  
    num[u] = 1;  
    for(int i = head[u];i != -1; i = edge[i].next)  
    {  
        int v = edge[i].to;  
        //因为路径是双向的,所以不能等于父节点
        if(v == pre)  continue;   
        dfs1(v,u,d+1);  
        //更新u节点的儿子数目  
        num[u] += num[v];  
        if(son[u] == -1 || num[v] > num[son[u]])//求出重儿子即:所有儿子中size 最大的节点v
                son[u] = v;  
    }  
}  

//因为对于轻儿子来说,top[u]=u,对于重儿子来说,如果son[v]!=-1,那么top[v]=top[son[v]]  
void getpos(int u,int sp) //第二遍dfs求出top和p  
{  
    top[u] = sp;  
    //先找重儿子  
    if(son[u] != -1)   // 存在重儿子
    {  
        //把点的位置标记一下  
        p[u] = pos++;   
        b[ p[u] ] = a[u];  // 原始点  u 的值为a[u] , u在线段树中的位置为p[u] ,那么
        fp[p[u]] = u;  
        //更新重儿子  
        getpos(son[u],sp);  
    }  
    //如果到了叶子节点  
    else  
    {  
        //不再向下dfs  
        p[u] = pos++;  
        b[ p[u] ] = a[u];
        fp[p[u]] = u;  
        return;  
    }  
    //更新其他的节点  
    for(int i = head[u] ; i != -1; i = edge[i].next)  
    {  
        int v = edge[i].to;  
        if(v != son[u] && v != fa[u])  
            getpos(v,v);   //  轻儿子的top[u]=u
    }  
}  

#define lson l,m,rt<<1  
#define rson m+1,r,rt<<1|1   
struct seg{
    int l,r;
    int lazy;
    int v;
}st[MAXN<<2];
void push_down(int rt){  // 这个pushdown 每次都写得很艰难啊  , 这样写的话 需要放在每一次更新和询问的第一行。
    if(st[rt].lazy!=0  ){
        // printf("pushdown :%d,%d ------%d\n",st[rt].l,st[rt].r, st[rt].lazy );
        st[rt].v+=st[rt].lazy;
        if(st[rt].l!=st[rt].r){ 
            st[rt<<1].lazy+=st[rt].lazy;
            st[rt<<1|1].lazy+=st[rt].lazy; 
        }
        st[rt].lazy=0;
    }
}


void build(int l,int r,int rt){  
    st[rt].lazy=st[rt].v=0;
    st[rt].l=l;
    st[rt].r=r;
        if(l==r){  
            st[rt].v=b[l];
            return;  
      }  
       int m=(l+r)>>1;  
      build(lson);  
      build(rson);   
}  

void update(int v,int L,int R,int l,int r,int rt){  // 区间更新
         push_down(rt);
       if( L<=l &&r<=R){ 
            // printf("up :%d %d %d\n",st[rt].l,st[rt].r ,v);
                        st[rt].lazy+=v;
            return;  
         }  
         int m=(l+r)>>1;  
         if(m>=L)  
            update(v,L,R,lson);  
    if(R>m)  
                  update(v,L,R,rson);   
}  

int query(int p,int l,int r,int rt){    // 单点询问
     push_down(rt);
    if( l==r && l==p){  
        return st[rt].v;  
    }  
    // printf("qur:%d %d %d    p=%d   push%d\n",l,r,rt,p ,st[rt].lazy);
    int m=(l+r)>>1;   
    if(p<=m)  
        return query(p,lson);
    else 
       return query(p,rson); 
}  

void _find(int u,int v,int k){    
    int f1=top[u],f2=top[v];//先找到两个端点的重链顶端节点,如果是轻儿子,就是它本身  
    int temp=0;  
    while(f1!=f2){  
        //从深度较深的开始查询  
        if(deep[f1]<deep[f2]){  
            swap(f1,f2);  
            swap(u,v);  
        }  
        //查区间改变
        update(k,p[f1],p[u],1,n,1);   //  u----top[u]    在dfs的时候显然top先放入线段树
        u=fa[f1];f1=top[u];  
    }  
    //如果f1=f2代表在同一条重链上m,如果u=v 那么也需要单点更新
    // if(u==v)  
    //     return ;
    if(deep[u]>deep[v])  
        swap(u,v);  
     update(k,p[u],p[v],1,n,1) ;   // v-----u  deep[u]小 那自然先放入线段树
}  
char op[5];
int main(){  
    int t;  
    freopen("1.txt","r",stdin);
    int q,m;
     while(~scanf("%d %d %d",&n,&m,&q) ){
         init();
          for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
          int u,v;
          for(int i=1;i<n;i++){
            scanf("%d %d",&u,&v);
            addedge(u,v);
            addedge(v,u);
          }
          dfs1(1,0,1);
          getpos(1,1);
          build(1,n,1);
          int  k;
          while(q--){
            scanf("%s",op);
            if(op[0]=='I'){ //+
                scanf("%d %d %d",&u,&v,&k);
                _find(u,v,k);
            }
            else if(op[0]=='D'){  // -
            scanf("%d %d %d",&u,&v,&k);
                _find(u,v,-1*k);
            }
            else{
                scanf("%d",&u);
                printf("%d\n",query(p[u],1,n,1));
            } 
            // printf("\n\nq=%d\n",q);
            //  for(int i=1;i<=n;i++){
            //  printf("i=%d  p=%d %d\n",i,p[i],query(p[i],1,n,1));
              // }
          }
     }
    return 0;  
}  

wa一次是因为在_find里面 如果 u==v那么不会进行更新,我们也应该对其进行单点更新。顺便有更新了一波线段树push_down写法,(滑稽

3.BZOJ1036: [ZJOI2008]树的统计Count (对点进行操作)

也是裸的树链剖分,不过注意以下几个点:
1.有可能是负数,所以取maxx的时候ans初始值为很大的负数,不能为0
2.进行单点更新是 , 很容易写成更新u , 应该是更新p[u]
3.顺便记得 init ,先输入数组,在dfs,getpos ,build 等等
wa+2


const int MAXN = 30010;  
struct Edge  
{  
    int to,next;  
}edge[MAXN*2];  
int head[MAXN],tot;  

int top[MAXN];//top[v]表示v所在的重链的顶端节点  
int fa[MAXN]; //父亲节点  
int deep[MAXN];//深度  
int num[MAXN];//num[v]表示以v为根的子树的节点数  
int p[MAXN];//p[v]表示点 在线段树中的位置  
int fp[MAXN];//和p数组相反  即 记录线段树中的某个位置 存的是那个点
int son[MAXN];//重儿子  
int pos;   // 每个点在线段树中的位置。

int n;    // 记得 n一定要开全局
  int  a[MAXN];  // 原始点  u 的值为a[u] , u在线段树中的位置为p[u]  
  int b[MAXN];   // 线段树  b[ p[u] ]=a[u];
void init()  
{  
    tot = 0;  
    memset(head,-1,sizeof(head));  
    pos = 1;//边的序号其实是从1开始?  
    memset(son,-1,sizeof(son));  
}  
void addedge(int u,int v)  
{  
    edge[tot].to = v;edge[tot].next = head[u];head[u] = tot++;  
}  
void dfs1(int u,int pre,int d) //第一遍dfs求出fa,deep,num,son  
{  
    deep[u] = d;  
    fa[u] = pre;  
    num[u] = 1;  
    for(int i = head[u];i != -1; i = edge[i].next)  
    {  
        int v = edge[i].to;  
        //因为路径是双向的,所以不能等于父节点
        if(v == pre)  continue;   
        dfs1(v,u,d+1);  
        //更新u节点的儿子数目  
        num[u] += num[v];  
        if(son[u] == -1 || num[v] > num[son[u]])//求出重儿子即:所有儿子中size 最大的节点v
                son[u] = v;  
    }  
}  

//因为对于轻儿子来说,top[u]=u,对于重儿子来说,如果son[v]!=-1,那么top[v]=top[son[v]]  
void getpos(int u,int sp) //第二遍dfs求出top和p  
{  
    top[u] = sp;  
    //先找重儿子  
    if(son[u] != -1)   // 存在重儿子
    {  
        //把点的位置标记一下  
        p[u] = pos++;   
        b[ p[u] ] = a[u];  // 原始点  u 的值为a[u] , u在线段树中的位置为p[u] ,那么
        fp[p[u]] = u;  
        //更新重儿子  
        getpos(son[u],sp);  
    }  
    //如果到了叶子节点  
    else  
    {  
        //不再向下dfs  
        p[u] = pos++;  
        b[ p[u] ] = a[u];
        fp[p[u]] = u;  
        return;  
    }  
    //更新其他的节点  
    for(int i = head[u] ; i != -1; i = edge[i].next)  
    {  
        int v = edge[i].to;  
        if(v != son[u] && v != fa[u])  
            getpos(v,v);   //  轻儿子的top[u]=u
    }  
}  

#define lson l,m,rt<<1  
#define rson m+1,r,rt<<1|1   
struct seg{
    int l,r; 
    int v;
    int maxx;  // 最大值
    int sum;   // 权值之和
}st[MAXN<<2];
void push_up(int rt){
    st[rt].maxx= max(st[rt<<1].maxx,st[rt<<1|1].maxx) ;
    st[rt].sum=st[rt<<1].sum  + st[rt<<1|1].sum  ;
}

void build(int l,int r,int rt){   
    st[rt].l=l;
    st[rt].r=r;
    st[rt].maxx=st[rt].v=0;
        if(l==r){  
            st[rt].v=st[rt].maxx=st[rt].sum=b[l];
            // printf("l=%d  %d\n",l,b[l]);
            return;  
      }  
       int m=(l+r)>>1;  
      build(lson);  
      build(rson);   
      push_up(rt);
}  

void update(int p, int v,int l,int r,int rt){  //  单点
       if( l==r ){ 
            // printf("up :%d %d %d\n",st[rt].l,st[rt].r ,v);
                        st[rt].v=v;
                        st[rt].sum=v;
                        st[rt].maxx=v;
            return;  
         }  
         int m=(l+r)>>1;  
         if(p<=m)  
                  update(p,v,lson);  
        else 
                  update(p,v,rson);   
           push_up(rt);
}  

int query(int L,int  R,int l,int r,int rt,int k){    //  区间 
    if(  L <=l && r<=R){  
        // printf("qur:%d %d===%d  %d\n",st[rt].l ,st[rt].r ,st[rt].maxx,st[rt].sum);
        if(k==1) return st[rt].maxx;
        else return st[rt].sum;  
    }   
    int m=(l+r)>>1;   
    int ans;
    if(k==1) ans=-1000000000;
    else ans=0;
    if( m>=L) {   // 
        if(k==1)   ans=max(ans,query(L,R,lson,k)  );
        else ans+=query(L,R,lson,k);
    }
     if(m<R){
         if(k==1)    ans=max(ans,query(L,R,rson,k) ); 
         else  ans+=query(L,R,rson,k);
     }
     push_up(rt);
     return ans;
}  

void _find(int u,int v,int k){    
    // printf("\n\n %d %d %d\n",u,v,k);
    int f1=top[u],f2=top[v];//先找到两个端点的重链顶端节点,如果是轻儿子,就是它本身  
    int temp=0;
    int ans;  
    if(k==2) ans=0; 
    else ans=-100000000;
    while(f1!=f2){  
        //从深度较深的开始查询  
        if(deep[f1]<deep[f2]){  
            swap(f1,f2);  
            swap(u,v);  
        }  
        //查区间改变 
        // printf("qujian1: %d %d ----------%d %d  ans:%d\n",u,f1,p[f1],p[u],ans);
        if(k==1)  ans=max(ans,query(p[f1],p[u],1,n,1,k)  );   //  u----top[u]    在dfs的时候显然top先放入线段树
        else ans+=query(p[f1],p[u],1,n,1,k) ;
        u=fa[f1];f1=top[u];  
    }  
    //如果f1=f2代表在同一条重链上m,如果u=v 那么也需要单点更新
    // if(u==v)  
    //     return ;
    if(deep[u]>deep[v])  
        swap(u,v);  
        // printf("qujian2: %d %d ----------%d %d  ans:%d\n",u,v,p[u],p[v],ans);
     if(k==1) ans=max(ans,  query(p[u],p[v],1,n,1,k)  );   // v-----u  deep[u]小 那自然先放入线段树
     else ans+=query(p[u],p[v],1,n,1,k);
     printf("%d\n",ans);
}  
char op[10];
int  main(){
    //freopen("1.txt","r",stdin);
    while(~scanf("%d",&n)){
        init();
        int u,v;
        for(int i=1;i<n;i++){
            scanf("%d %d",&u,&v);
            addedge(u,v);
            addedge(v,u);
        }
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
        dfs1(1,0,1);
        getpos(1,1);
        build(1,n,1);
        int q;
        scanf("%d",&q);
        while(q--){
            scanf("%s %d %d",op,&u,&v); 
            if(op[0]=='C'){
                update(p[u],v,1,n,1);   // 更新不是 u 啊,写的也太随意了吧。
            }
            else{
                if(op[1]=='M'){
                    _find(u,v,1);
                }
                else{
                    _find(u,v,2); 
                }
            }
        }
    }
}

http://acm.fzu.edu.cn/problem.php?pid=2082

四、FOJ Problem 2082 过路费(对边进行操作)

没有什么难点,感觉对边的操作没有对点的操作那么熟练,
主要是需要牢记p[u]:代表的是 u与父亲节点连边在线段树中的位置,
因此加完边之后需要更新下val数组。 但是需要先 dfs 和getpos ,因为更新val 时需要用到p数组

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
 /*
 */  
const int MAXN = 50010;  
struct Edge  
{  
    int to,next;  
}edge[MAXN*2];  
int head[MAXN],tot;  

int top[MAXN];//top[v]表示v所在的重链的顶端节点  
int fa[MAXN]; //父亲节点  
int deep[MAXN];//深度  
int num[MAXN];//num[v]表示以v为根的子树的节点数  
int p[MAXN];//p[v]表示v与其父亲节点的连边在线段树中的位置  
int fp[MAXN];//和p数组相反  即 记录线段树中的某个位置的边的起点?
int son[MAXN];//重儿子  
int pos;   // 每条边在线段树中的位置。

int n;  
int  val[MAXN];
void init()  
{  
    tot = 0;  
    memset(head,-1,sizeof(head));  
    pos = 1;//边的序号其实是从1开始?  
    memset(son,-1,sizeof(son));  
}  
void addedge(int u,int v)  
{  
    edge[tot].to = v;edge[tot].next = head[u];head[u] = tot++;  
}  
void dfs1(int u,int pre,int d) //第一遍dfs求出fa,deep,num,son  
{  
    deep[u] = d;  
    fa[u] = pre;  
    num[u] = 1;  
    for(int i = head[u];i != -1; i = edge[i].next)  
    {  
        int v = edge[i].to;  
        //因为路径是双向的,所以不能等于父节点
        if(v == pre)  continue;   
        dfs1(v,u,d+1);  
        //更新u节点的儿子数目  
        num[u] += num[v];  
        if(son[u] == -1 || num[v] > num[son[u]])//求出重儿子即:所有儿子中size 最大的节点v
                son[u] = v;  
    }  
}  

//因为对于轻儿子来说,top[u]=u,对于重儿子来说,如果son[v]!=-1,那么top[v]=top[son[v]]  
void getpos(int u,int sp) //第二遍dfs求出top和p  
{  
    top[u] = sp;  
    //先找重儿子  
    if(son[u] != -1)   // 存在重儿子
    {  
        //把边的位置标记一下  
        p[u] = pos++;  
        //fp相当于是p的反函数,即记录线段树中某个位置的边 的在树中的起点。
        fp[p[u]] = u;  
        //更新重儿子  
        getpos(son[u],sp);  
    }  
    //如果到了叶子节点  
    else  
    {  
        //不再向下dfs  
        p[u] = pos++;  
        fp[p[u]] = u;  
        return;  
    }  
    //更新其他的节点  
    for(int i = head[u] ; i != -1; i = edge[i].next)  
    {  
        int v = edge[i].to;  
        if(v != son[u] && v != fa[u])  
            getpos(v,v);   //  轻儿子的top[u]=u
    }  
}  

#define lson l,m,rt<<1  
#define rson m+1,r,rt<<1|1  
struct node{
    int l,r;
    ll v;
    ll sum;
}st[MAXN<<2];

void pushup(int rt){  
    // if(st[rt].l != st[rt].r) 
        st[rt].sum= st[rt<<1].sum + st[rt<<1|1].sum ;
}  

void build(int l,int r,int rt){  
    st[rt].l=l;
    st[rt].r=r;
          st[rt].v=st[rt].sum=0;
        if(l==r){  
            // printf("bd:%d   =%d\n",l,val[l]);
            st[rt].v=val[l];
            st[rt].sum=val[l];
            return;  
        }  
        int m=(l+r)>>1;  
        build(lson);  
        build(rson);  
        pushup(rt);  
}  

void update(int p,ll x,int l,int r,int rt){  // 单点更新
    if(l==r){  
        st[rt].v=x;
        st[rt].sum=x;
        return;  
    }  
    int m=(l+r)>>1;  
    if(p<=m)  
        update(p,x,lson);  
    else  
        update(p,x,rson);  
    pushup(rt);  
}  

ll query(int L,int R,int l,int r,int rt){    // 区间询问
    if( L<=l &&r<=R){  
        return st[rt].sum;  
    }
    int m=(l+r)>>1;  
    ll res=0;  
    if(m>=L) 
         res+= query(L,R,lson); 
    if(R>m)  
        res+= query(L,R,rson);   
    pushup(rt);
    return res;  
}

ll  _find(int u,int v){   // 找 a-----b 中所有路径的最大值。
    int f1=top[u],f2=top[v];//先找到两个端点的重链顶端节点,如果是轻儿子,就是它本身  
    ll temp=0;  
    while(f1!=f2){  
        //从深度较深的开始查询  
        if(deep[f1]<deep[f2]){  
            swap(f1,f2);  
            swap(u,v);  
        }
        //查询一条重链 
         temp+=query(p[f1],p[u],1,n,1)   ;
        u=fa[f1];f1=top[u];  
    }
    //如果f1=f2代表在同一条重链上m,如果u=v代表更新结束  ,这一步的时候  u从更深的地方跳上来,跳至同一重链, 如果u=v那么所有路径都走过一遍。
    if(u==v)  
        return temp;  
    if(deep[u]>deep[v])  
        swap(u,v);
    return  temp+query(p[son[u]],p[v],1,n,1);   
}  

ll e[MAXN][3];  
int main(){   
    freopen("1.txt","r",stdin);
        int q;
        while(~scanf("%d %d",&n,&q)){   
            init();  
            for(int i=1;i<n;i++){  
                scanf("%d %d %lld",&e[i][0],&e[i][1],&e[i][2]);  
                addedge(e[i][0],e[i][1]);  
                addedge(e[i][1],e[i][0]);  
            }  
             dfs1(1,0,0);  
            getpos(1,1);            // 现在已经把树剖分完毕。
            for(int i=1;i<n;i++){    
                if(deep[e[i][0]]<deep[e[i][1]])    // 调整每条路径的父亲,儿子关系。0>1 0为儿子1为父亲
                    swap(e[i][0],e[i][1]);  
                val[ p[e[i][0]] ]=e[i][2];   //  pi表示i和其父亲连边在线段树中的位置。
                // printf("val i=%d  %d  =%d\n",i,p[e[i][0]] ,e[i][2]);
            }  

            build(1,n,1);   // 以val建树
            int op;
            int u;
            ll v;   
            while(q--){ 
                scanf("%d %d %lld",&op,&u,&v);  
                if(op==0)  
                    update(p[e[u][0]],v,1,n,1);  
                else  
                    printf("%lld\n",_find(u,v));  
            }  
    }  

    return 0;  
}  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值