【问题描述】
现在给你一棵树,共有n个节点,由1至n编号,每条边有一个权值。现在你要完成一些指令,这些指令包括:
1、A i j v:将树边(i,j)的权值增加v。
2、Q a b:询问节点a到节点b的路径长度。
【输入格式】
第1行为两个整数n,m,表示树的节点数目和指令数目。接下来的n-1行每行分别有3个整数u,v,w,u和v两个整数表示该边的节点编号,w表示边的权值。然后是m条指令,指令格式见题目描述。
【输出格式】
对于每个查询指令,输出一个答案。
【输入样例】
6 3
1 2 3
2 5 2
6 2 1
3 1 4
1 4 5
Q 3 5
A 1 2 3
Q 6 4
【输出样例】
9
12
【数据范围】
1 < n,m <= 100000
任何时候,边权值为正整数,且不超过2*10^9。
题解:
乍一看是一道难题,仔细一看还不是一般的难题!询问节点a和b的距离比较好做,先生成每个点到根节点的距离dist,再求一下最近公共祖先LCA(a,b)就行了。但将一条边的权值增加v后,这条边所连接的子树上每一个点的dist都要增加v,暴力维护dist的时间复杂度是相当高的,所以最后的难点落在了如何维护dist上
我们将维护dist抽象成这样一个步骤:给定一个值v,使一个集合(也就是一颗子树)内的所有元素的值增加v。在竞赛范围内,我曾经接触过的用于维护子集合的值的数据结构,线段树和树状数组勉强算得上(序列也是集合嘛),但是如何吧树上的值转换到序列上去呢?
这里介绍个东西:DFS序!DFS序,简单地说就是一棵树DFS遍历的顺序(一般是先序遍历)。如图:
我们对这棵树从左往右先序遍历后,得到的节点编号序列为:
1、2、5、6、9、10、11、3、7、4、8
观察序列我们不难发现,以i为根节点的子树(包括i),在DFS序中是一段连续的区间。例:以2为根的子树,节点有2、5、6、9、10、11,在我们所得到的序列中是连续的一段,这是由DFS遍历的方式决定的
于是,我们就可以把对子树的修改转化为对序列上一个区间内元素的修改了!
具体实现方法可以用树状数组或线段树,这里选用了线段树
代码实现:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
typedef long long ll;
const int maxn=100005;
struct edge{
int to,next,w;
}e[maxn<<1];
int first[maxn]={0},np=0;
int fa[maxn][20]={0},dep[maxn]={0};
int l[maxn]={0},r[maxn]={0},idx[maxn]={0};
int n,m,tot=0;
int lc[maxn<<1],rc[maxn<<1],A[maxn],rt,nnp=0;
ll dist[maxn]={0},mv[maxn<<1],add[maxn<<1];
inline int in(){
int x=0,f=1;char ch=getchar();
while(ch<'0'|ch>'9'){if(ch=='-') f=-f;ch=getchar();}
while(ch<='9'&&ch>='0') x=x*10+ch-'0',ch=getchar();
return x*f;
}
void addedge(int u,int v,int w){
e[++np]=(edge){v,first[u],w},first[u]=np;
}
void DFS(int i,int f){
fa[i][0]=f;
for(int k=1;k<=18;k++) fa[i][k]=fa[fa[i][k-1]][k-1];
//l[i],r[i]表示以i为根的子树在DFS序上的区间,idx[i]表示DFS序上第i个元素对应的树上节点编号
l[i]=++tot,idx[tot]=i;
for(int p=first[i];p;p=e[p].next){
int j=e[p].to,c=e[p].w;
if(j==f) continue;
dist[j]=dist[i]+c,dep[j]=dep[i]+1;
//dist[i]表示i到根节点的距离,dep[i]表示i的深度
DFS(j,i);
}
r[i]=tot;
}
//............................线段树实现.............................
void build(int &now,int L,int R){
now=++nnp;
if(L==R){
mv[now]=dist[idx[L]];
return;
}
int m=(L+R)>>1;
build(lc[now],L,m);
build(rc[now],m+1,R);
}
void pushdown(int now){ //下传lazy操作
mv[lc[now]]+=add[now],mv[rc[now]]+=add[now];
add[lc[now]]+=add[now],add[rc[now]]+=add[now];
add[now]=0;
}
void update(int now,int L,int R,int i,int j,int d){
if(L>=i&&R<=j){
add[now]+=d,mv[now]+=d;
return;
}
pushdown(now);
int m=(L+R)>>1;
if(j<=m) update(lc[now],L,m,i,j,d);
else if(i>m) update(rc[now],m+1,R,i,j,d);
else{
update(lc[now],L,m,i,m,d);
update(rc[now],m+1,R,m+1,j,d);
}
}
ll query(int now,int L,int R,int i){
if(L==i&&R==i) return mv[now]; //单点查找
pushdown(now);
int m=(L+R)>>1;
if(i<=m) return query(lc[now],L,m,i);
else return query(rc[now],m+1,R,i);
}
//..........................线段树实现完成...........................
int LCA(int u,int v){
if(dep[u]<dep[v]) swap(u,v);
int x=dep[u]-dep[v];
for(int k=18;k>=0;k--) if(x&(1<<k)) u=fa[u][k];
if(u==v) return u;
for(int k=18;k>=0;k--) if(fa[u][k]!=fa[v][k]) u=fa[u][k],v=fa[v][k];
return fa[u][0];
}
int main(){
//freopen("in.txt","r",stdin);
n=in(),m=in();
int u,v,w;
for(int i=1;i<n;i++){
u=in(),v=in(),w=in();
addedge(u,v,w);
addedge(v,u,w);
}
DFS(1,0);
build(rt,1,n);
char op[5];
ll du,dv,dz;
int z;
for(int i=1;i<=m;i++){
scanf("%s",op);
if(op[0]=='A'){
u=in(),v=in(),w=in();
if(dep[u]<dep[v]) update(rt,1,n,l[v],r[v],w);
else update(rt,1,n,l[u],r[u],w);
}
else{ //两点间距离
u=in(),v=in();
z=LCA(u,v);
du=query(rt,1,n,l[u]);
dv=query(rt,1,n,l[v]);
dz=query(rt,1,n,l[z]);
printf("%lld\n",du+dv-2*dz);
}
}
return 0;
}