题目描述
一棵树上有 n 个节点,编号分别为 1 到 n,每个节点都有一个权值 w。
我们将以下面的形式来要求你对这棵树完成一些操作:
I. CHANGE u t : 把结点 u 的权值改为 t。
II. QMAX u v: 询问从点 u 到点 v 的路径上的节点的最大权值。
III. QSUM u v: 询问从点 u 到点 v 的路径上的节点的权值和。
注意:从点 u 到点 v 的路径上的节点包括 u 和 v 本身。
输入格式
输入文件的第一行为一个整数 n,表示节点的个数。
接下来 n-1 行,每行 2 个整数 a 和 b,表示节点 a 和节点 b 之间有一条边相连。
接下来一行 n 个整数,第 i 个整数 wi 表示节点 i 的权值。
接下来 1 行,为一个整数 q,表示操作的总数。
接下来 q 行,每行一个操作,以 CHANGE u t
或者 QMAX u v
或者 QSUM u v
的形式给出。
输出格式
对于每个 QMAX 或者 QSUM 的操作,每行输出一个整数表示要求输出的结果。
输入样例
4
1 2
2 3
4 1
4 2 1 3
12
QMAX 3 4
QMAX 3 3
QMAX 3 2
QMAX 2 3
QSUM 3 4
QSUM 2 1
CHANGE 1 5
QMAX 3 4
CHANGE 3 6
QMAX 3 4
QMAX 2 4
QSUM 3 4
输出样例
4
1
2
2
10
6
5
6
5
16
中文题目,大意很明确了,下面看看树链剖分的内容
对于一般的线段树,我们可以做的是区间查询,单点修改,区间修改,对于此题给出的从一个节点到另一个节点路径上的权值和,我们首先要想办法把它建成线段树,我们要做的是把给的一个树(图),按某种规律放在一个数组中,对这个数组建新树,这样就可以操作
定义几个概念
一个节点儿子最多的儿子,定义为自己的重儿子,否则为自己的轻儿子。
对于一个非叶子节点,有且仅有一个重儿子,对于叶子节点,没有重儿子。
由重儿子和父亲连成的边叫重边,否则为轻边,重边组成的链叫做重链。
我们把给定图放在数组中的规律为,找到该图的dfs序(即,dfs的顺序)我们先dfs一遍,找到重儿子,再次dfs对其编号,放在数组中,保证优先放入重儿子,如此,重链的顺序是连续的。
代码详解
//树的统计
//树链剖分
#include<stdio.h>
#include<algorithm>
#include<iostream>
#include<vector>
#include<cstring>
using namespace std;
#define ll long long
#define lson zz<<1
#define rson zz<<1|1
const int maxn = 1e6+6;
vector<int> edge[maxn];
string s;
int n,q,x,y;
int father[maxn],dep[maxn],size[maxn],son[maxn],seg[maxn],rev[maxn*4];
int top[maxn],sum[maxn*4],mx[maxn],w[maxn];
inline int read() {
int x = 0, f = 1; char ch = getchar();
while (!isdigit(ch)) {
if (ch == '-') f = -1;
ch = getchar();
}
while (isdigit(ch)) {
x = (x << 1) + (x << 3) + (ch & 15);
ch = getchar();
}
return x * f;
}
//father[x] 记录x在树中的父亲
//dep[x] 记录x在树中的深度
//size[x] 记录x的子树的节点数
//son[x] x的重儿子编号
//top[x] x所在重链的顶部节点
//seg[x] x在线段树的下标
//rev[x] 线段树中第x个位置对应树中的节点。
//top[x] x这个点所在重链的最顶端的点
void dfs1(int u,int fa){
int v;
size[u]=1;//当前节点的大小初始化为1,算自己。
father[u]=fa;//当前节点的父亲节点是 fa
dep[u]=dep[fa]+1;//当前节点的深度是 父亲节点+1
for(int i=0;i<edge[u].size();i++){
if(edge[u][i]!=fa){//无向的图,所以防止回去
v=edge[u][i];//v就是u的儿子了
dfs1(v,u);//再次dfs
size[u]+=size[v];//u的大小就是他自己加上子节点的大小
if(size[v]>size[son[u]])//找到重儿子
son[u]=v;
}
}
}
//seg[x] x在线段树的下标 rev[x] 线段树中第x个位置对应原来的节点。
void dfs2(int u,int fa){
int v;
seg[u]=++seg[0];//seg[0]初始是0,后面也没用,这里用来做seg的计数变量,是u在线段树里的下标
rev[seg[0]]=u;//线段树里下标是seg[0]的对应原来的节点为u;
top[u]=top[fa]; //与父亲节点的top相同
if(son[u])
dfs2(son[u],u);//优先重儿子编号
for(int i=0;i<edge[u].size();i++){//重儿子安排好后,考虑其他的
v=edge[u][i];
if(!seg[v]) dfs2(v,v);
}
}
void pushup(int zz){
sum[zz]=sum[lson]+sum[rson];
mx[zz]=max(mx[lson],mx[rson]);
}
void build(int l,int r,int zz){
if(r==l)
mx[zz]=sum[zz]=w[rev[l]];
else{
int mid=(l+r)>>1;
build(l,mid,lson);
build(mid+1,r,rson);
pushup(zz);
}
}
void change(int zz,int q,int l,int r,int k){
int mid=(l+r)>>1;
if(l==r) sum[zz]=mx[zz]=k;
else{
if(q<=mid) change(lson,q,l,mid,k);
else change(rson,q,mid+1,r,k);
pushup(zz);
}
}
int checksum(int zz,int l,int r,int nl,int nr){
int ans=0;
if(nl<=l&&r<=nr) return sum[zz];
else{
int mid=(l+r)>>1;
if(nl<=mid) ans+=checksum(lson,l,mid,nl,nr);
if(nr>mid) ans+=checksum(rson,mid+1,r,nl,nr);
return ans;
}
}
int checkmax(int zz,int l,int r,int nl,int nr){
//nl,nr 是查询的 x,y 在线段树的下标
int ans=-4*maxn;
if(nl<=l&&nr>=r) return mx[zz];
else{
int mid=(l+r)>>1;
if(nl<=mid) ans=max(ans,checkmax(lson,l,mid,nl,nr));
if(nr>mid) ans = max(ans,checkmax(rson,mid+1,r,nl,nr));
return ans;
}
}
int qmax(int u,int v ){
int ans = -4*maxn;
int fu=top[u],fv=top[v];
while (fu!=fv)//u,v 两个的top不同,不在同一个重链
{
if(dep[fu]<dep[fv]){//哪个的深,就先移动哪个
swap(u,v);
swap(fu,fv);//交换位置
}
//seg[x] x在线段树的下标 rev[x] 线段树中第x个位置对应原来的节点。
ans=max(ans,checkmax(1,1,n,seg[fu],seg[u]));//在u-fu所在重链范围中找到max
u=father[fu];fu=top[u];//到顶以后,找到top[fu]是自己,但是father不是
}
if(dep[u]>dep[v]) swap(u,v);
ans=max(ans,checkmax(1,1,n,seg[u],seg[v]));
return ans;
}
int qsum(int u,int v){
int ans=0;
int fu=top[u],fv=top[v];
while (fu!=fv)//同理,不同重链的弄到同一个上
{
if(dep[fu]<dep[fv]){
swap(u,v);
swap(fu,fv);
}
ans+=checksum(1,1,n,seg[fu],seg[u]);
u=father[fu];fu=top[u];
}
if(dep[u]>dep[v]) swap(u,v);
ans+=checksum(1,1,n,seg[u],seg[v]);
return ans;
}
int main(){
n=read();
for(int i=1;i<n;i++){
x=read(),y=read();
edge[x].push_back(y);
edge[y].push_back(x);//建立边。
}
for(int i=1;i<=n;i++) w[i]=read();
for(int i=1;i<=n;i++) top[i]=i;//所有的top先初始化为自己
q=read();
dfs1(1,0);
dfs2(1,1);
build(1,n,1);
while (q--)
{
cin>>s;
x=read(),y=read();
if(s=="CHANGE"){
change(1,seg[x],1,seg[0],y);
}
if(s=="QMAX"){
printf("%d\n",qmax(x,y));
}
if(s=="QSUM"){
printf("%d\n",qsum(x,y));
}
}
return 0;
}