树链剖分,计算机术语,指一种对树进行划分的算法,它先通过轻重边剖分将树分为多条链,保证每个点属于且只属于一条链,然后再通过数据结构(树状数组、BST、SPLAY、线段树等)来维护每一条链。
——百度百科

重链剖分

概念 1 重儿子

一个父节点的所有儿子中,子树节点最大(\(siz\) 最大)的节点。

记为 \(son[u]=v\)

概念2 轻儿子

父节点所有儿子中,除过重儿子的所有节点。

概念3 重边

由父亲节点和重儿子连接成的边。

概念4 轻边

由父亲节点和轻儿子连接成的边。

概念5 重链

由多条重边连成的链。

概念5 轻边

由多条轻边连成的链。

image

实现思路

  1. 对于一个节点先找出它所在的子树大小,同时我们可以得到它的所有子节点的子树大小 \(siz[v]\),这样我们可以得到此节点的重儿子。

例如,点 1 的三个儿子分别是 2,3,4。
2 所在的子树大小为 5,
3 所在的子树大小为 2,
4 所在的子树大小为 6,
那么 1 的重儿子就是 4。

  1. \(dfs\) 的过程中顺便记录节点 \(u\) 的父亲 \(f[u]\),从根节点的深度 \(dep[u]\) 等。

  2. 再来一遍 \(dfs\),连接重链,标记每个节点的 \(dfs\) 序,处理出每个节点所在重链的顶点 \(top[u]\) 和节点编号 \(id[u]\)

代码实现

第一遍 dfs

作用主要是找出每个节点的深度重儿子

int son[500010],deep[500101],f[500101];
int siz[500010];
//son:重儿子
//deep:节点深度
//f:节点的父节点 
void dfs1(int u,int fa)
{
	f[u]=fa;
	deep[u]=deep[fa]+1;
	siz[u]=1;
	for(int i=head[u];i;i=edge[i].next)
	{
		int v=edge[i].to;
		if(v==fa)continue;
		dfs1(v,u);
		siz[u]+=siz[v];
		if(siz[son[u]]<siz[v])
		son[u]=v;
	}
}

第二遍 dfs

作用主要是求出每条重链的顶点和节点的 \(dfs\) 序。

//rk:节点编号 
//id:dfn序
//top:重链顶端 
void dfs2(int u,int t)
{
	top[u]=t;
	if(son[u])dfs2(son[u],t);
	id[u]=++cnt;
	rk[cnt]=u;
	for(int i=head[u];i;i=edge[i].next)
	{
		int v=edge[i].to;
		if(v==son[u]||v==f[u])continue;
		dfs2(v,v);
	}
}

这样就完成了树链剖分的基础操作(链式前向星存图)。

线段树操作总结

单点加,区间加

void add(int now,int l,int r,int k)
{
	tr[now].lazy+=k;
	tr[now].sum+=k*(r-l+1);
}
void pushdown(int now,int l,int r)
{
	if(!tr[now].lazy) return ;
	int mid=(l+r)>>1;
	add(lid,l,mid,tr[now].lazy);
	add(rid,mid+1,r,tr[now].lazy);
	tr[now].lazy=0; 
}
void change(int now,int l,int r,int x,int y,int k)
{
	if(x<=l&&r<=y)
	{
		add(now,l,r,k);return;
	}
	pushdown(now,l,r);
	int mid=(l+r)>>1;
	if(x<=mid) change(lid,l,mid,x,y,k);
	if(y>mid) change(rid,mid+1,r,x,y,k);
	tr[now].sum=tr[lid].sum+tr[rid].sum;
} 

单点查,区间查

最基础的线段树操作之一。
别忘了 \(pushdown\)

int query(int now,int l,int r,int x,int y)
{
	if(x<=l&&r<=y)	return tr[now].sum;
	pushdown(now,l,r);
	int mid=(l+r)>>1;
	int res=0;
	if(x<=mid) res+=query(lid,l,mid,x,y);
	if(y>mid) res+=query(rid,mid+1,r,x,y);
	return res; 
}

子树加

通过调用区间加的函数来实现。

一个节点 \(u\) 的子树的 \(dfs\) 序的范围是 \([id[u],id[u]+siz[u]-1]\)

其中 \(siz[u]\) 表示子树大小,在 \(dfs1\) 中求得。

void add_tree(int u,int k)
{
	change(1,1,n,id[u],id[u]+siz[u]-1,k);
}

树链加

代码基本同求 \(lca\)

void add_path(int u,int v,int k)
{
	while(top[u]!=top[v])
	{
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
		change(1,1,n,id[top[u]],id[u],k);
		u=f[top[u]];
	}
	if(dep[u]>dep[v])swap(u,v);
	change(1,1,n,id[u],id[v],k);
}

子树查

代码同子树加。只不过调用的函数不同。

int query_tree(int u)
{
	return query(1,1,n,id[u],id[u]+siz[u]-1);
}

树链查

代码同树链加,也是调用的函数不同。

int query_path(int u,int v)
{
	int res=0;
	while(top[u]!=top[v])
	{
		if(dep[top[u]]<dep[top[v]])swap(u,v);
		res+=query(1,1,n,id[top[u]],id[u]);
		u=f[top[u]];
	}
	if(dep[u]>dep[v])swap(u,v);
	res+=query(1,1,n,id[u],id[v]);
	return res;
}

边权下放

从网上学的,详见例题 3。

题目讲解

例题 1:P3384 【模板】重链剖分/树链剖分

简明地讲,这道题就是用线段树维护一个树链剖分,要做到子树改,树链改,子树查,树链查。

#include<bits/stdc++.h> 
using namespace std;
#define N 200001
int w[N];
int n,m,p,s;
struct node{
	int to,next;
}edge[N];
int head[N],ccnt;
inline void add(int u,int v)
{
	edge[++ccnt].next=head[u];
	edge[ccnt].to=v;
	head[u]=ccnt;
}
int dep[N],son[N],f[N],siz[N];
void dfs1(int u,int fa)
{
	dep[u]=dep[fa]+1;
	f[u]=fa;
	siz[u]=1;
	for(int i=head[u];i;i=edge[i].next)
	{
		int v=edge[i].to;
		if(v==fa) continue;
		dfs1(v,u);
		siz[u]+=siz[v];
		if(siz[son[u]]<siz[v])son[u]=v;
	}
}
int top[N],rk[N],id[N],nw[N];
int cnt;
void dfs2(int u,int t)
{
	top[u]=t,id[u]=++cnt,nw[cnt]=w[u];
	if(!son[u])return ;
	dfs2(son[u],t);
//	rk[cnt]=u;
	for(int i=head[u];i;i=edge[i].next)
	{
		int v=edge[i].to;
		if(v==son[u]||v==f[u])continue;
		dfs2(v,v);
	}
}
class wme{
    private:
        struct nodee
        {
    	    int num,lazy;
        }tr[N<<2];
    public:
        #define lid now<<1
        #define rid now<<1|1
        void build(int now,int l,int r)
        {
        	if(l==r)
        	{
        		tr[now].num=nw[r];
				return ;
			}
            int mid=(l+r)>>1;
            build(lid,l,mid),build(rid,mid+1,r);
            tr[now].num=(tr[lid].num+tr[rid].num)%p;
        }
        void Add(int now,int l,int r,int k)
        {
            tr[now].num+=k*(r-l+1);
            tr[now].lazy+=k;
        }
        void pushdown(int now,int l,int r)
        {
            if(!tr[now].lazy)return;
            int mid=(l+r)>>1;
            Add(lid,l,mid,tr[now].lazy);
            Add(rid,mid+1,r,tr[now].lazy);
            tr[now].lazy=0;
        }
        void upd(int now,int l,int r,int x,int y,int k)
        {
            if(x<=l&&r<=y)
            {
                Add(now,l,r,k);return ;
            }
            pushdown(now,l,r);
            int mid=(l+r)>>1;
            if(x<=mid)upd(lid,l,mid,x,y,k);
            if(y>mid) upd(rid,mid+1,r,x,y,k);
            tr[now].num=(tr[lid].num+tr[rid].num)%p;
        }
        void upd_Path(int u,int v,int k)
        {
            while(top[u]!=top[v])
            {
                if(dep[top[u]]<dep[top[v]])swap(u,v);
                upd(1,1,n,id[top[u]],id[u],k);
                u=f[top[u]];
            }
            if(dep[u]<dep[v])swap(u,v);
            upd(1,1,n,id[v],id[u],k);
        }
        void upd_Tree(int u,int k)
        {
            upd(1,1,n,id[u],id[u]+siz[u]-1,k);
        }
        long long query(int now,int l,int r,int x,int y)
        {
            if(x<=l&&r<=y) return tr[now].num;
            pushdown(now,l,r);
            int mid=(l+r)>>1;
            long long res=0;
            if(x<=mid) res+=query(lid,l,mid,x,y);
            if(y>mid) res+=query(rid,mid+1,r,x,y);
            return res%p;
        }
        long long query_Path(int u,int v)
        {
            long long res=0;
            while(top[u]!=top[v])
            {
                if(dep[top[u]]<dep[top[v]])u^=v^=u^=v;
                res+=query(1,1,n,id[top[u]],id[u]);
                u=f[top[u]];
            }res%=p;
            if(dep[u]<dep[v])u^=v^=u^=v;
            res+=query(1,1,n,id[v],id[u]);
            return res;
        }
        long long query_Tree(int u)
        {
            return query(1,1,n,id[u],id[u]+siz[u]-1);
        }
}st;
signed main()
{
	cin>>n>>m>>s>>p;
	for(int i=1;i<=n;i++) cin>>w[i];
	for(int i=1;i<n;i++)
	{
		int u,v;
		cin>>u>>v;
		add(u,v),add(v,u);
	}
	dfs1(s,0);
	dfs2(s,s);
    st.build(1,1,n);
    while(m--)
    {
        int t,u,v,k;
        cin>>t>>u;
        if(t==1)
        {
            cin>>v>>k;
            st.upd_Path(u,v,k);
        }
        else if(t==2)
        {
            cin>>v;
            cout<<st.query_Path(u,v)%p<<endl;
        }
        else if(t==3)
        {
            cin>>k;
            st.upd_Tree(u,k);
        }
        else cout<<st.query_Tree(u)%p<<endl;
    }
    return 0;
}

例题 2:#P396. 「ZJOI 2008」树的统计

单点修改,树链求和、最大值。

最大值的求法与求和大同小异。
核心代码:

long long query_Pathmax(int u,int v)
{
	long long res=-1145141919;
	while(top[u]!=top[v])
	{
		if(dep[top[u]]<dep[top[v]])swap(u,v);
		res=max(res,querymax(1,1,n,id[top[u]],id[u]));
		u=f[top[u]];
	}
	if(dep[u]<dep[v])swap(u,v);
	res=max(res,querymax(1,1,n,id[v],id[u]));
	return res;
}

例题 3:#P398. [SPOJ375 QTREE]难存的情缘

这道题给出了书上每条边的权值,会修改某条边的权值和查询两点树链边权最大值。

难点在边权下放修改上。我参考了别人的思路,用了几个 vector 去搞。

记录每条边的下方节点,把边权转化成点权。

void dfs1(int u)
{
	siz[u]=1;
	for(int i=0;i<edge[u].size();i++)
	{
		int v=edge[u][i];
		if(!siz[v])
		{
			dep[v]=dep[u]+1;f[v]=u;
			indexnode[Index[u][i]]=v;//重点
			a[v]=W[u][i];
			dfs1(v);
			siz[u]+=siz[v];
			if(siz[v]>siz[son[u]])
				son[u]=v;
		}
	}
}
for(int i=1;i<n;i++)
	{
		cin>>u>>v>>w;
		Index[u].push_back(i);//Index 向量和 indexnode 数组完成了边权下放
		Index[v].push_back(i);//
		edge[u].push_back(v);
		edge[v].push_back(u);
		W[u].push_back(w);
		W[v].push_back(w);
	//	add(u,v,w),add(v,u,w);
	}

还有一点,在 query_path 的时候,不能包含两点的 \(lca\),因为 \(lca\) 的点权是 \(lca\) 上面的边的,不包含在树链中。

完整代码:

#include<bits/stdc++.h>
using namespace std;
int n,m;
const int N=2e4+2;
int ww[N];
vector<int>Index[N],edge[N],W[N];
int indexnode[N];
int a[N];
int son[N],siz[N],f[N],dep[N];
void dfs1(int u)
{
	siz[u]=1;
	for(int i=0;i<edge[u].size();i++)
	{
		int v=edge[u][i];
		if(!siz[v])
		{
			dep[v]=dep[u]+1;f[v]=u;
			indexnode[Index[u][i]]=v;
			a[v]=W[u][i];
			dfs1(v);
			siz[u]+=siz[v];
			if(siz[v]>siz[son[u]])
				son[u]=v;
		}
	}
}
int top[N],nw[N],id[N],cnt;
void dfs2(int u,int t)
{
	top[u]=t;
	id[u]=++cnt;
	nw[cnt]=u;
	if(son[u])dfs2(son[u],t);
	for(int i=0;i<edge[u].size();i++)
	{
		int v=edge[u][i];
		if(v==son[u]||v==f[u])continue;
		dfs2(v,v);
	}
}
class wmw{
	private:
		struct nodee{
			int mx;
		}tr[N<<2];
	public:
		#define lid now<<1
		#define rid now<<1|1
		void build(int now,int l,int r)
		{
			if(l==r)
			{
				tr[now].mx=a[nw[l]];return ;
			}
			int mid=(l+r)>>1;
			build(lid,l,mid),build(rid,mid+1,r);
			tr[now].mx=max(tr[lid].mx,tr[rid].mx);
		}
		void change(int now,int l,int r,int x,int y,int k)
		{
			if(x<=l&&r<=y)
			{
				tr[now].mx=k;
				return ;
			} 
			int mid=(l+r)>>1;
			if(x<=mid) change(lid,l,mid,x,y,k);
			if(y>mid) change(rid,mid+1,r,x,y,k);
			tr[now].mx=max(tr[lid].mx,tr[rid].mx);
		}
		int query(int now,int l,int r,int x,int y)
		{
			if(x<=l&&r<=y)
			{
				return tr[now].mx;
			}
			int res=-INT_MAX-1;
			int mid=(l+r)>>1;
			if(x<=mid) res=max(res,query(lid,l,mid,x,y));
			if(y>mid) res=max(res,query(rid,mid+1,r,x,y));
			return res;
		}
		int query_path(int u,int v)
		{
			int res=-1145141919; 
			while(top[u]!=top[v])
			{
				if(dep[top[u]]<dep[top[v]])swap(u,v);
				res=max(res,query(1,1,n,id[top[u]],id[u]));
				u=f[top[u]];
			}
			if(dep[u]>dep[v]) swap(u,v);
			res=max(res,query(1,1,n,id[u]+1,id[v]));
			return res;
		}
}st;
signed main()
{
	cin>>n;int u,v,w;
	for(int i=1;i<n;i++)
	{
		cin>>u>>v>>w;
		Index[u].push_back(i);
		Index[v].push_back(i);
		edge[u].push_back(v);
		edge[v].push_back(u);
		W[u].push_back(w);
		W[v].push_back(w);
	}
	dfs1(1);
	dfs2(1,1);
	st.build(1,1,n);
	
	string c;cin>>c;
	while(c!="DONE")
	{
		int x,y;
		cin>>x>>y;
		if(c[0]=='Q')
		{
			cout<<st.query_path(x,y)<<endl;
		}
		else if(c[0]=='C')
		{
			x=indexnode[x];
			st.change(1,1,n,id[x],id[x],y);
		}
		cin>>c;
	}
}

例题 4:P3178 [HAOI2015] 树上操作

板子题。题目要求单点修改,子树修改,树链查询。

但是树链查询是从根节点到某一节点的,比较弱化,可以使用差分去做,但是我选择树剖+线段树。

子树查询代码:

void add_tree(int u,int k)
{
	change(1,1,n,id[u],id[u]+siz[u]-1,k);
}

例题 5:P3833 [SHOI2012] 魔法树

这道题核心考察了树链加。

依然是 lca 的代码,改几下就好了。

void add_path(int u,int v,int k)
{
	while(top[u]!=top[v])
	{
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
		change(1,1,n,id[top[u]],id[u],k);
		u=f[top[u]];
	}
	if(dep[u]>dep[v])swap(u,v);
	change(1,1,n,id[u],id[v],k);
}

例题 6:P4281 [AHOI2008] 紧急集合 / 聚会

这道题只需要求出 \(lca\) 即可。但是在求两点距离的时候,不需要用线段树维护,去 query_path

只需要用到 \(dep\) 数组。

int dis(int u,int v)
{
	int lc=lca(u,v);
	return dep[u]+dep[v]-2*dep[lc];
}

例题 7:P3979 遥远的国度

是个可爱的小紫。

和「BZOJ3306」树 这道题很像啊,换根树剖。

搞清楚当前 \(root\) 的深度和查询的 \(u\) 的关系就行了。

int getans(int u)
{ 
	if(root==u)	return st.query(1,1,n,1,n);
	if(dep[u]>=dep[root])	return st.query_tree(u);
 	if(dep[u]<dep[root]&&f[lca(u,root)]==u)
	{
		int ff=lca(u,root);
		return min(st.query(1,1,n,1,id[ff]-1),st.query(1,1,n,id[ff]+siz[ff],n));
	}
	else return st.query_tree(u);
}

例题 8:P2146 [NOI2015] 软件包管理器

全世界的 OI 同仁都应该写一下这道题。。

浪费了我整整半天的时间,重构了一遍。

  • 对于 \(install\) 操作,先 query_path(1,u),再 upd_Path(1,u,1),输出 \(dep[u]\) 减去刚刚 query_path(1,u) 的值。

  • 对于 \(uninstall\) 操作,输出 st.query_Tree(u),再 upd_Tree(u,0) 修改子树。

代码里面总共有 3 个 bug。

  1. dfs2 里的条件
void dfs2(int u,int t)
{
	top[u]=t;id[u]=++cnt;
	if(!son[u]) return ;
	dfs2(son[u],t);
	for(int i=0;i<edge[u/*这里,原本写的是i*/].size();i++)
	{
		int v=edge[u][i];
		if(v==f[u]||v==son[u]) continue;
		dfs2(v,v);
	}
 }
  1. \(pushdown\)
void pushdown(int now,int l,int r)
{
	if(tr[now].lazy==-1) return ;
	tr[lid].lazy=tr[rid].lazy=tr[now].lazy;
	//下面的两个 r-l+1 都错了,应该是 l-mid+1 和 r-mid
	tr[lid].sum=(r-l+1)*tr[now].lazy;
	tr[rid].sum=(r-l+1)*tr[now].lazy;
	tr[now].lazy=-1;
}
  1. 修改 \(pushdown\) 之后 \(pushdown\) 还是错的
void add(int now,int l,int r,int k)
{
	tr[now].lazy=k;
	//下面的 (r-l+1)*k 原本没有乘 k
	tr[now].sum=(r-l+1)*k;
}
void pushdown(int now,int l,int r)
{
	if(tr[now].lazy!=-1){
		int mid=(l+r)>>1;
		add(lid,l,mid,tr[now].lazy);
		add(rid,mid+1,r,tr[now].lazy);
		tr[now].lazy=-1;
	}
}

例题 8:P4315 月下“毛景树”

整整一天!!!!

细节爆炸。

新的边权下放方法(链式前向星)

难点在于边权下放和 \(pushdown\)

void pushdown(int now,int l,int r)
{
	if(tr[now].tag!=-1)
	{
		tr[lid].mx=tr[rid].mx=tr[now].tag+tr[now].lazy;
		tr[lid].tag=tr[rid].tag=tr[now].tag+tr[now].lazy;
		tr[lid].lazy=tr[rid].lazy=0;//在区间覆盖时,子树的区间修改懒标记要清零!!
		tr[now].lazy=0;
		tr[now].tag=-1;
	}
	if(tr[now].lazy)
	{
		tr[lid].lazy+=tr[now].lazy;
		tr[rid].lazy+=tr[now].lazy;
		tr[lid].mx+=tr[now].lazy;
		tr[rid].mx+=tr[now].lazy;
		tr[now].lazy=0;
	}
}

最难绷的是,第一遍写,调完交上去只过了第 11 个点,

第二遍写,调完交上去只有第 11 个点没过。

原来是有个数据要 change_path(2,2,5),由于某些神秘特性,第一次写的 \(change \ path\) 屏蔽了此操作,

第二次写的却完美踩坑。

因此我加了特判:

	if(c[1]=='d')
	{
	int u,v,w;cin>>u>>v>>w;
	if(u==v) continue;//这里
	st.change_path(u,v,w);
	}

最终 AC 了。

$\color{red}{点击查看完整代码}$
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e5+2;
struct node{
	int to,next,w;
}edge[N<<1];
int head[N],cnt;int n,Val[N];
void add(int u,int v,int w)
{
	edge[++cnt].next=head[u];
	edge[cnt].to=v;
	edge[cnt].w=w;
	head[u]=cnt;
}
int siz[N],son[N],dep[N],f[N];
void dfs1(int u)
{
	siz[u]=1;
	for(int i=head[u];i;i=edge[i].next)
	{
		int v=edge[i].to,w=edge[i].w;
		if(!siz[v])
		{
			dep[v]=dep[u]+1,f[v]=u;
			Val[v]=w;//边权下放
			dfs1(v);
			siz[u]+=siz[v];
			if(siz[son[u]]<siz[v]) son[u]=v;
		 } 
	}
}
int top[N],id[N],tim,nw[N];
void dfs2(int u,int t)
{
	id[u]=++tim,top[u]=t,nw[tim]=u;
	if(!son[u]) return ;
	dfs2(son[u],t);
	for(int i=head[u];i;i=edge[i].next)
	{
		int v=edge[i].to;
		if(v==f[u]||v==son[u]) continue;
		dfs2(v,v);
	}
}
class wmw{
	private:
		struct ndoee{
			int tag,lazy,mx;
		}tr[N<<2];
	public:
		#define lid now<<1
		#define rid now<<1|1
		void build(int now,int l,int r)
		{
			tr[now].tag=-1;
			if(l==r)
			{
				tr[now].mx=Val[nw[l]];return ;
			}
			int mid=(l+r)>>1;
			build(lid,l,mid),build(rid,mid+1,r);
			tr[now].mx=max(tr[lid].mx,tr[rid].mx);
		}
		void pushdown(int now,int l,int r)
		{
			if(tr[now].tag!=-1)
			{
				tr[lid].mx=tr[rid].mx=tr[now].tag+tr[now].lazy;
				tr[lid].tag=tr[rid].tag=tr[now].tag+tr[now].lazy;
				tr[lid].lazy=tr[rid].lazy=0;
				tr[now].lazy=0;
				tr[now].tag=-1;
			}
			if(tr[now].lazy)
			{
				tr[lid].lazy+=tr[now].lazy;
				tr[rid].lazy+=tr[now].lazy;
				tr[lid].mx+=tr[now].lazy;
				tr[rid].mx+=tr[now].lazy;
				tr[now].lazy=0;
			}
		}
		void cover(int now,int l,int r,int x,int y,int k)
		{
			if(x<=l&&r<=y)
			{
				tr[now].lazy=0;
				tr[now].tag=k,tr[now].mx=k;return ;
			}
			int mid=(l+r)>>1;
			pushdown(now,l,r);
			if(x<=mid) cover(lid,l,mid,x,y,k);
			if(y>mid) cover(rid,mid+1,r,x,y,k);
			tr[now].mx=max(tr[lid].mx,tr[rid].mx);
		}
		void change(int now,int l,int r,int x,int y,int k)
		{
			if(x<=l&&r<=y)
			{
				tr[now].lazy+=k,tr[now].mx+=k;return ;
			}
			int mid=(l+r)>>1;
			pushdown(now,l,r);
			if(x<=mid) change(lid,l,mid,x,y,k);
			if(y>mid) change(rid,mid+1,r,x,y,k);
			tr[now].mx=max(tr[lid].mx,tr[rid].mx);
		}
		void cover_path(int u,int v,int k)
		{
			while(top[u]!=top[v])
			{
				if(dep[top[u]]<dep[top[v]]) swap(u,v);
				cover(1,1,n,id[top[u]],id[u],k);
				u=f[top[u]];
			}
			if(dep[u]>dep[v]) swap(u,v);
			cover(1,1,n,id[u]+1,id[v],k);
		}
		void change_path(int u,int v,int k)
		{
			while(top[u]!=top[v])
			{
				if(dep[top[u]]<dep[top[v]]) swap(u,v);
				change(1,1,n,id[top[u]],id[u],k);
				u=f[top[u]];
			}
			if(dep[u]>dep[v]) swap(u,v);
			change(1,1,n,id[u]+1,id[v],k);
		}
		int query(int now,int l,int r,int x,int y)
		{
			if(x<=l&&r<=y)
			{
				return tr[now].mx;
			}
			int mid=(l+r)>>1,res=-1145141919;
			pushdown(now,l,r);
			if(x<=mid) res=max(res,query(lid,l,mid,x,y));
			if(y>mid) res=max(res,query(rid,mid+1,r,x,y));
			return res;
		}
		int query_path(int u,int v)
		{
			int res=-1145141919;
			while(top[u]!=top[v])
			{
				if(dep[top[u]]<dep[top[v]]) swap(u,v);
				res=max(res,query(1,1,n,id[top[u]],id[u]));
				u=f[top[u]];
			}
			if(dep[u]>dep[v]) swap(u,v);
			res=max(res,query(1,1,n,id[u]+1,id[v]));
			return res;
		}
}st;
signed main()
{
	cin>>n;
	for(int i=1;i<n;i++)
	{
		int u,v,w;
		cin>>u>>v>>w;
		add(u,v,w),add(v,u,w);
	}
	dfs1(1);
//	printf("ced\n");
	dfs2(1,1);
//	printf("ced\n");
	
	st.build(1,1,n);
	while(191981)
	{
	//	cout<<st.query_path(2,1)<<endl;
		string c;
		cin>>c;
		if(c[1]=='d')
		{
			int u,v,w;cin>>u>>v>>w;
			if(u==v) continue;//特判
			st.change_path(u,v,w);
		}
		else if(c[2]=='v')
		{
			int u,v,w;
			cin>>u>>v>>w;
			st.cover_path(u,v,w);
		}
		else if(c[2]=='a')
		{
			int u,v;
			cin>>u>>v;
			u=dep[edge[u*2-1].to]<dep[edge[u*2].to]?edge[u*2].to:edge[u*2-1].to;//神奇的链式前向星边权下放
		//	cout<<u<<endl;
			st.cover(1,1,n,id[u],id[u],v);
		}
		else if(c[2]=='x')
		{
			int u,v;cin>>u>>v;
			cout<<st.query_path(u,v)<<endl;
		}
		else return 0;
	}
}