定义:

  将整棵树剖分为若干条链,使它组合成线性结构,然后用其他的数据结构维护信息。就是一种技巧,为了方便处理问题。
  形式:重链剖分,长链剖分等。此处讲述重链剖分。

重链剖分:

  可以将树上的任意一条路径划分成不超过 l o g n logn logn 条连续的链,每条链上的点深度互不相同(即是自底向上的一条链,链上所有点的 L C A LCA LCA 为链的一个端点)。还能保证划分出的每条链上的节点 D F S DFS DFS 序连续。
  树上每个节点都属于且仅属于一条重链 。

性质:

(1)重链开头的结点不一定是重子节点;
(2)所有的重链将整棵树完全剖分 ;
(3)在剖分时<mark>优先遍历重儿子</mark>,最后每条重链的 D F S DFS DFS 序就会是连续的;
(4)一棵子树内各点在 d f n dfn dfn 数组中的编号是连续的;
(5)当向下经过一条轻边时,所在子树的大小至少会除以2;
(6)对于树上的任意一条路径,把它拆分成从 l c a lca lca 分别向两边往下走,分别最多走 l o g n logn logn 次。因此,<mark>树上的每条路径都可以被拆分成不超过 l o g n logn logn 条重链</mark>。

实现:

相关数组定义:

f a [ x ] : fa[x]: fa[x]: 表示节点 x x x 在树上的父亲;
d e p t h [ x ] : depth[x]: depth[x]: 表示节点 x x x 在树上的深度;
s i z e [ x ] : size[x]: size[x]: 表示以节点 x x x 为根的子树的节点个数;
s o n [ x ] : son[x]: son[x]: 表示节点 x x x 的重儿子 ;
t o p [ x ] : top[x]: top[x]: 表示节点 x x x 所在重链的顶部节点(深度最小);
d f n [ x ] : dfn[x]: dfn[x]: 表示节点 x x x D F S DFS DFS 序编号,即生成 d f s dfs dfs 序时,节点 x x x r n k rnk rnk 中出现的编号;
r n k [ x ] : rnk[x]: rnk[x]:表示 D F S DFS DFS 遍历各点时所对应的节点编号,与 d f n [ x ] dfn[x] dfn[x] 表示的意义相反。
具体过程可以分为两遍 d f s dfs dfs:
第一遍:求出 f a [ x ] , d e p t h [ x ] , s i z e [ x ] , s o n [ x ] fa[x],depth[x],size[x],son[x] fa[x],depth[x],size[x],son[x] 的值。
大致过程和 L C A LCA LCA d f s dfs dfs 过程大致相同。

void dfs1(int v,int p,int d)//当前点,父亲节点,深度
{
    fa[v]=p;//存父亲
    depth[v]=d;//记深度
    size[v]=1;//子树大小
    for(int i=0;i<pic[v].size();i++)
    {
        int t=pic[v][i];
        if(t!=p)
        {
            dfs1(t,v,d+1);
            size[v]+=size[t];//回溯过程求子树的大小
            if(son[v]==-1||size[t]>size[son[v]])//更新重儿子
                son[v]=t;
        }
    }
}

第二遍 d f s dfs dfs :求出 t o p , d f n , r n k top,dfn,rnk top,dfn,rnk 数组。
  此处的 d f s dfs dfs 序生成 r n k rnk rnk 数组和基于 r m q rmq rmq l c a lca lca 中的 d f s dfs dfs 序有所不同,每个点只在 r n k rnk rnk 数组中<mark>出现一次</mark>。
  经过此次的 d f s dfs dfs ,原来的树就变成一维的有序序列。而且,同一条链上的点在序列中连续,深度小的点位置靠前。但是对于一条边相连的两个点,如果不在同一条链上,在序列中的位置就不在一起。整体的效果就相当于把整棵树,劈成不同的链,然后按照一定的顺序,把各条链摆放在 r n k rnk rnk 数组中。

void dfs2(int v,int p,int tp,int &cnt)//当前点,当前点的父亲节点,链顶点,rnk中编号
{
    top[v]=tp;//v所在的链的端点
    dfn[v]=cnt;//在rnk数组中的编号
    rnk[cnt++]=v;
    if(son[v]==-1)//链的末尾
        return;
    dfs2(son[v],v,tp,cnt);//优先遍历重儿子,保证同一条重链上的点的dfs序连续
    for(int i=0;i<pic[v].size();i++)
    {
        int t=pic[v][i];
        if(t!=son[v]&&t!=p)
            dfs2(t,v,t,cnt);//链的起始
    }
}
应用:

1.求 l c a lca lca
  不断向上跳重链,当跳到同一条重链上时,深度较小的结点即为 L C A LCA LCA。<mark>向上跳重链时需要先跳所在重链顶端深度较大的那个</mark>。
  常数更小。

int lca(int u, int v) 
{
    while (top[u]!=top[v]) 
    {
        if(depth[top[u]]>depth[top[v]])
            u=fa[top[u]];
        else
            v=fa[top[v]];
  	}
  	return depth[u]<=depth[v]?u:v;
}

2.子树维护:
  比如将以 x x x 为根的子树的所有结点的权值增加。在 D F S DFS DFS 搜索的时候,<mark>子树中的结点的 D F S DFS DFS 序是连续的</mark>。每一个结点记录 bottom 表示所在子树连续区间末端的结点。这样就把子树信息转化为连续的一段区间信息。
3.路径上维护:
用树链剖分求树上两点路径权值和,伪代码如下:

用线段树或树状数组维护。

例题:

1.Query on a tree SPOJ - QTREE
问题:
1.改变某一条边权
2.求两点之间的最大边权
用树链剖分预处理,用线段树维护边权。求两点之间的最大边权时,在求 l c a lca lca 跳链的过程中用线段树求。

#include <bits/stdc++.h>
using namespace std;
const int N=1e4+5;
typedef long long ll;
struct edge
{
    int from,to;
    ll w;
};
ll tree[N<<2];
int size[N],son[N],depth[N],fa[N],dfn[N],rnk[N],top[N];
vector<int>pic[N];
vector<edge>eg;
void dfs1(int v,int p,int d)
{
    fa[v]=p;
    depth[v]=d;
    size[v]=1;
    for(int i=0;i<pic[v].size();i++)
    {
        int t=pic[v][i];
        if(t!=p)
        {
            dfs1(t,v,d+1);
            size[v]+=size[t];
            if(son[v]==-1||size[t]>size[son[v]])
                son[v]=t;
        }
    }
}
void dfs2(int v,int p,int tp,int &cnt)
{
    top[v]=tp;
    dfn[v]=cnt;
    rnk[cnt++]=v;
    if(son[v]==-1)//链的末尾
        return;
    dfs2(son[v],v,tp,cnt);//优先遍历重儿子,保证同一条重链上的点的dfs序连续
    for(int i=0;i<pic[v].size();i++)
    {
        int t=pic[v][i];
        if(t!=son[v]&&t!=p)
            dfs2(t,v,t,cnt);//链的起始
    }
}
void build(int l,int r,int rt)
{
    if(l==r)
    {
        tree[rt]=0;
        return;
    }
    int mid=(l+r)>>1;
    build(l,mid,rt<<1);
    build(mid+1,r,rt<<1|1);
    tree[rt]=max(tree[rt<<1],tree[rt<<1|1]);
}
void update(int l,int r,int c,int rt,ll num)
{
    if(l==r)
    {
        tree[rt]=num;
        return;
    }
    int mid=(l+r)>>1;
    if(c<=mid)
        update(l,mid,c,rt<<1,num);
    else if(c>mid)
        update(mid+1,r,c,rt<<1|1,num);
    tree[rt]=max(tree[rt<<1],tree[rt<<1|1]);
}
ll query(int L,int R,int l,int r,int rt)
{
    if(L<=l&&r<=R)
        return tree[rt];
    int mid=(l+r)>>1;
    ll ans=0;
    if(L<=mid)
        ans=max(ans,query(L,R,l,mid,rt<<1));
    if(R>mid)
        ans=max(ans,query(L,R,mid+1,r,rt<<1|1));
    return ans;
}
ll lca_ans(int u,int v,int n)
{
    ll ans=0;
    while(top[u]!=top[v])//在求lca的过程中求最大边权
    {
        if(depth[top[u]]<depth[top[v]])
            swap(u,v);
        ans=max(ans,query(dfn[top[u]],dfn[u],1,n,1));
        u=fa[top[u]];//换链
    }
    if(u!=v)
    {
        if(depth[u]<depth[v])
            ans=max(ans,query(dfn[u]+1,dfn[v],1,n,1));
        else
            ans=max(ans,query(dfn[v]+1,dfn[u],1,n,1));
    }
    return ans;
}
int main()
{
    int t,n;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        int u,v;
        ll w;
        for(int i=1;i<=n;i++)
            pic[i].clear();
        eg.clear();
        memset(son,-1,sizeof(son));
        for(int i=1;i<=n;i++)
            top[i]=i;
        for(int i=1;i<n;i++)
        {
            scanf("%d%d%lld",&u,&v,&w);
            pic[u].push_back(v);
            pic[v].push_back(u);
            eg.push_back({u,v,w});
        }
        dfs1(1,1,0);
        int cnt=1;
        dfs2(1,-1,1,cnt);
        build(1,n,1);
        for(int i=0;i<n-1;i++)
        {
            edge e=eg[i];
            u=max(dfn[e.from],dfn[e.to]);
            update(1,n,u,1,e.w);
        }
        char ss[10]={0};
        while(1)
        {
            scanf("%s",ss);
            if(strcmp(ss,"DONE")==0)
                break;
            if(strcmp(ss,"CHANGE")==0)
            {
                scanf("%d%lld",&u,&w);
                edge e=eg[u-1];
                u=max(dfn[e.from],dfn[e.to]);
                update(1,n,u,1,w);
            }
            else if(strcmp(ss,"QUERY")==0)
            {
                scanf("%d%d",&u,&v);
                printf("%lld\n",lca_ans(u,v,n));
            }
        }
        if(t)
            puts("");
    }
    return 0;
}
/* 2 9 1 2 1 1 3 1 1 4 2 2 6 1 2 7 1 3 5 2 5 9 1 4 8 2 */

Aragorn’s Story HDU - 3966【模板】

#include <bits/stdc++.h>
using namespace std;
const int N=5e4+5000;
int son[N],size[N],fa[N],top[N],depth[N],dfn[N],rnk[N];
int tree[N<<2],a[N],lazy[N<<2];
vector<int>pic[N];
void dfs1(int v,int p,int d)
{
    fa[v]=p;
    depth[v]=d;
    size[v]=1;
    for(int i=0;i<pic[v].size();i++)
    {
        int t=pic[v][i];
        if(t!=p)
        {
            dfs1(t,v,d+1);
            size[v]+=size[t];
            if(son[v]==-1||size[t]>size[son[v]])
                son[v]=t;
        }
    }
}
void dfs2(int v,int p,int tp,int &cnt)
{
    top[v]=tp;
    rnk[cnt]=v;
    dfn[v]=cnt++;
    if(son[v]==-1)
        return;
    dfs2(son[v],v,tp,cnt);
    for(int i=0;i<pic[v].size();i++)
    {
        int t=pic[v][i];
        if(son[v]!=t&&p!=t)
            dfs2(t,v,t,cnt);
    }
}
void build(int l,int r,int rt)
{
    if(l==r)
    {
        lazy[rt]=0;
        tree[rt]=a[rnk[l]];
        return;
    }
    int mid=(l+r)>>1;
    build(l,mid,rt<<1);
    build(mid+1,r,rt<<1|1);
    tree[rt]=tree[rt<<1]+tree[rt<<1|1];
}
void pushdown(int rt,int ln,int rn)
{
    if(lazy[rt])
    {
        lazy[rt<<1]+=lazy[rt];
        tree[rt<<1]+=lazy[rt]*ln;
        lazy[rt<<1|1]+=lazy[rt];
        tree[rt<<1|1]+=lazy[rt]*rn;
        lazy[rt]=0;
    }
}
void update(int l,int r,int L,int R,int num,int rt)
{
    if(L<=l&&r<=R)
    {
        tree[rt]+=num*(r-l+1);
        lazy[rt]+=num;
        return;
    }
    int mid=(l+r)>>1;
    pushdown(rt,mid-l+1,r-mid);
    if(L<=mid)
        update(l,mid,L,R,num,rt<<1);
    if(R>mid)
        update(mid+1,r,L,R,num,rt<<1|1);
    tree[rt]=tree[rt<<1]+tree[rt<<1|1];
}
int query(int l,int r,int L,int R,int rt)
{
    if(L<=l&&r<=R)
        return tree[rt];
    int mid=(l+r)>>1;
    pushdown(rt,mid-l+1,r-mid);
    int ans=0;
    if(L<=mid)
        ans+=query(l,mid,L,R,rt<<1);
    if(R>mid)
        ans+=query(mid+1,r,L,R,rt<<1|1);
    return ans;
}
void path_update(int u,int v,int w,int n)
{
    while(top[u]!=top[v])
    {
        if(depth[top[u]]<depth[top[v]])
            swap(u,v);
        update(1,n,dfn[top[u]],dfn[u],w,1);
        u=fa[top[u]];
    }
    if(depth[u]>depth[v])
        swap(u,v);
    update(1,n,dfn[u],dfn[v],w,1);
}
int main()
{
    int n,m,p;
    while(scanf("%d%d%d",&n,&m,&p)!=EOF)
    {
        int u,v,w,cnt=1;
        char ss[8]={0};
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
        for(int i=1;i<=n;i++)
            pic[i].clear();
        memset(son,-1,sizeof(son));
        memset(lazy,0,sizeof(lazy));
        for(int i=1;i<=m;i++)
        {
            scanf("%d%d",&u,&v);
            pic[u].push_back(v);
            pic[v].push_back(u);
        }
        dfs1(1,1,0);
        dfs2(1,1,1,cnt);
        build(1,n,1);
        for(int i=1;i<=p;i++)
        {
            scanf("%s",ss);
            //用一个字符+getchar:re数组访问越界
            //getchar();
            //char cc;
            //scanf("%c",&cc);
            if(ss[0]=='Q')
            {
                scanf("%d",&u);
                printf("%d\n",query(1,n,dfn[u],dfn[u],1));
            }
            else if(ss[0]=='I')
            {
                scanf("%d%d%d",&u,&v,&w);
                path_update(u,v,w,n);
            }
            else if(ss[0]=='D')
            {
                scanf("%d%d%d",&u,&v,&w);
                path_update(u,v,-w,n);
            }
        }
    }
    return 0;
}
/* 3 2 5 1 2 3 2 1 2 3 I 1 3 5 Q 2 D 1 2 2 Q 1 Q 3 */

Tree HDU - 6547
1.将一条链上的所有节点的值开根号向下取整;
2.求一条链上值的和;
由数据范围,控制开根号的次数有限,所以可以对每个点就行更新。

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5;
typedef long long ll;
ll tree[N<<2];
ll a[N];
vector<int>pic[N];
int n;
int son[N],size[N],depth[N],fa[N],dfn[N],top[N],rnk[N];
void dfs1(int v,int p,int d)
{
    fa[v]=p;
    depth[v]=d;
    size[v]=1;
    for(int i=0;i<pic[v].size();i++)
    {
        int u=pic[v][i];
        if(u!=p)
        {
            dfs1(u,v,d+1);
            size[v]+=size[u];
            if(son[v]==-1||size[son[v]]<size[u])
                son[v]=u;
        }
    }
}
void dfs2(int v,int p,int tp,int &cnt)
{
    top[v]=tp;
    dfn[v]=cnt;
    rnk[cnt++]=v;
    if(son[v]==-1)
        return;
    dfs2(son[v],v,tp,cnt);
    for(int i=0;i<pic[v].size();i++)
    {
        int u=pic[v][i];
        if(u!=p&&u!=son[v])
            dfs2(u,v,u,cnt);
    }
}
void build(int l,int r,int rt)
{
    if(l==r)
    {
        tree[rt]=a[rnk[l]];
        return;
    }
    int mid=(l+r)>>1;
    build(l,mid,rt<<1);
    build(mid+1,r,rt<<1|1);
    tree[rt]=tree[rt<<1]+tree[rt<<1|1];
}
void update(int l,int r,int L,int R,int rt)
{
    if(tree[rt]<=r-l+1)
        return;
    if(l==r)
    {
        tree[rt]=(ll)sqrt(1.0*tree[rt]);
        return;
    }
    int mid=(l+r)>>1;
    if(L<=mid)
        update(l,mid,L,R,rt<<1);
    if(R>mid)
        update(mid+1,r,L,R,rt<<1|1);
    tree[rt]=tree[rt<<1]+tree[rt<<1|1];
}
ll query(int l,int r,int L,int R,int rt)
{
    if(L<=l&&r<=R)
        return tree[rt];
    int mid=(l+r)>>1;
    ll ans=0;
    if(L<=mid)
        ans+=query(l,mid,L,R,rt<<1);
    if(R>mid)
        ans+=query(mid+1,r,L,R,rt<<1|1);
    return ans;
}
void path_update(int u,int v)
{
    while(top[u]!=top[v])
    {
        if(depth[top[u]]<depth[top[v]])
            swap(u,v);
        update(1,n,dfn[top[u]],dfn[u],1);
        u=fa[top[u]];
    }
    if(depth[u]<depth[v])
        swap(u,v);
    update(1,n,dfn[v],dfn[u],1);
}
ll path_query(int u,int v)
{
    ll ans=0;
    while(top[u]!=top[v])
    {
        if(depth[top[u]]<depth[top[v]])
            swap(u,v);
        ans+=query(1,n,dfn[top[u]],dfn[u],1);
        u=fa[top[u]];//cout<<"ans="<<ans<<endl;
    }
    if(depth[u]<depth[v])
        swap(u,v);
    ans+=query(1,n,dfn[v],dfn[u],1);
    return ans;
}
int main()
{
    int q,op,u,v;
    scanf("%d%d",&n,&q);
    for(int i=1;i<=n;i++)
        scanf("%lld",&a[i]);
    memset(son,-1,sizeof(son));
    for(int i=1;i<n;i++)
    {
        scanf("%d%d",&u,&v);
        pic[u].push_back(v);
        pic[v].push_back(u);
    }
    int cnt=1;
    dfs1(1,1,0);
    dfs2(1,1,1,cnt);
    build(1,n,1);
    while(q--)
    {
        scanf("%d%d%d",&op,&u,&v);
        if(op==0)
            path_update(u,v);
        else if(op==1)
            printf("%lld\n",path_query(u,v));
    }
    return 0;
}

染色 HYSBZ - 2243

题意:
给定一棵有 n n n 个节点的无根树和m个操作,操作有 2 2 2 类:

  1. 将节点 a a a 到节点 b b b 路径上所有点都染成颜色 c c c
  2. 询问节点 a a a 到节点 b b b 路径上的颜色段数量(连续相同颜色被认为是同一段);

如“112221”由3段组成:“11”、“222”和“1”。
请你写一个程序依次完成这 m m m 个操作。
分析:
  树剖之后用线段树维护区间颜色段数,区间查询和区间修改。线段树结点中维护的有:段数,左端点颜色,右端点颜色和懒惰标记。

  • 当区间合并时,若左孩子的右端点颜色和右孩子的左端点颜色相同,那么段数要 1 -1 1
  • 区间修改时注意维护左右端点的颜色;
  • 查询时若左右子区间连接处的颜色相同,段数 1 -1 1
  • 跳链时,求出当前链的颜色段数后,要比较该段链的顶点和其父亲的颜色是否相同,如果是,则 1 -1 1,直到两点处于同一条链为止。

错误:本来跳链时应该优先跳所在链顶点深度大的,即:

if(depth[top[u]]>depth[top[v]]) swap(u,v);

但是写成了

 if(depth[u]>depth[v]) swap(u,v);

W A WA WA 了好几次。

代码:

#include <bits/stdc++.h>
#define pb push_back
using namespace std;
const int N=1e5+5;
struct node
{
    int sum,lc,rc;
}tree[N<<2];
int lazy[N<<2],top[N],dfn[N],fa[N],depth[N],sz[N],son[N],c[N],rnk[N];
vector<int>pic[N];
int n;
void dfs1(int v,int p,int d)
{
    sz[v]=1;
    son[v]=-1;
    depth[v]=d;
    fa[v]=p;
    for(int i=0;i<pic[v].size();i++)
    {
        int u=pic[v][i];
        if(u!=p)
        {
            dfs1(u,v,d+1);
            sz[v]+=sz[u];
            if(son[v]==-1||sz[u]>sz[son[v]])
                son[v]=u;
        }
    }
}
void dfs2(int v,int p,int tp,int &cnt)
{
    top[v]=tp;
    dfn[v]=++cnt;
    rnk[cnt]=v;
    if(son[v]==-1)
        return;
    dfs2(son[v],v,tp,cnt);
    for(int i=0;i<pic[v].size();i++)
    {
        int u=pic[v][i];
        if(u!=p&&u!=son[v])
            dfs2(u,v,u,cnt);
    }
}
void pushup(int rt)
{
    tree[rt].sum=tree[rt<<1].sum+tree[rt<<1|1].sum;
    if(tree[rt<<1].rc==tree[rt<<1|1].lc)
        tree[rt].sum--;
    tree[rt].lc=tree[rt<<1].lc;
    tree[rt].rc=tree[rt<<1|1].rc;
}
void pushdown(int rt)
{
    if(lazy[rt]>-1)
    {
        tree[rt<<1].sum=tree[rt].sum;
        tree[rt<<1].lc=lazy[rt];
        tree[rt<<1].rc=lazy[rt];
        lazy[rt<<1]=lazy[rt];
        tree[rt<<1|1].sum=tree[rt].sum;
        tree[rt<<1|1].lc=lazy[rt];
        tree[rt<<1|1].rc=lazy[rt];
        lazy[rt<<1|1]=lazy[rt];
        lazy[rt]=-1;
    }
}
void build(int l,int r,int rt)
{
    lazy[rt]=-1;
    if(l==r)
    {
        tree[rt].sum=1;
        tree[rt].lc=c[rnk[l]];
        tree[rt].rc=c[rnk[l]];
        return;
    }
    int mid=(l+r)>>1;
    build(l,mid,rt<<1);
    build(mid+1,r,rt<<1|1);
    pushup(rt);
}
void update(int l,int r,int L,int R,int val,int rt)
{
    if(L<=l&&r<=R)
    {
        tree[rt].sum=1;
        tree[rt].lc=val;
        tree[rt].rc=val;
        lazy[rt]=val;
        return;
    }
    pushdown(rt);
    int mid=(l+r)>>1;
    if(L<=mid)
        update(l,mid,L,R,val,rt<<1);
    if(R>mid)
        update(mid+1,r,L,R,val,rt<<1|1);
    pushup(rt);
}
node query(int l,int r,int L,int R,int rt)
{
    if(L<=l&&r<=R)
        return tree[rt];
    node ans,t1,t2;
    int mid=(l+r)>>1;
    pushdown(rt);
    if(R<=mid)
        return query(l,mid,L,R,rt<<1);
    else if(L>mid)
        return query(mid+1,r,L,R,rt<<1|1);
    else
    {
        t1=query(l,mid,L,R,rt<<1);
        t2=query(mid+1,r,L,R,rt<<1|1);
        ans.sum=t1.sum+t2.sum;
        if(t1.rc==t2.lc)
            ans.sum--;
        ans.lc=t1.lc;
        ans.rc=t2.rc;
        return ans;
    }
}
void change(int u,int v,int c)
{
    while(top[u]!=top[v])
    {
        if(depth[top[u]]>depth[top[v]])
            swap(u,v);
        update(1,n,dfn[top[v]],dfn[v],c,1);
        v=fa[top[v]];
    }
    if(depth[u]>depth[v])
        swap(u,v);
    update(1,n,dfn[u],dfn[v],c,1);
}
int ask(int u,int v)
{
    node t;
    int ans=0;
    while(top[u]!=top[v])
    {
        if(depth[top[u]]>depth[top[v]])
            swap(u,v);
        t=query(1,n,dfn[top[v]],dfn[v],1);
        ans+=t.sum;
        if(query(1,n,dfn[fa[top[v]]],dfn[fa[top[v]]],1).rc==t.lc)
            ans--;
        v=fa[top[v]];
    }
    if(depth[u]>depth[v])
        swap(u,v);
    t=query(1,n,dfn[u],dfn[v],1);
    ans+=t.sum;
    return ans;
}
int main()
{
    int m,u,v,cc;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d",&c[i]);
    for(int i=1;i<n;i++)
    {
        scanf("%d%d",&u,&v);
        pic[u].pb(v);
        pic[v].pb(u);
    }
    char op[5];
    int cnt=0;
    dfs1(1,1,0);
    dfs2(1,0,1,cnt);
    build(1,n,1);
    for(int i=1;i<=m;i++)
    {
        scanf("%s",op);
        if(op[0]=='C')
        {
            scanf("%d%d%d",&u,&v,&cc);
            change(u,v,cc);
        }
        else if(op[0]=='Q')
        {
            scanf("%d%d",&u,&v);
            printf("%d\n",ask(u,v));
        }
    }
    return 0;
}