洛谷 P2590·树的统计 (树链剖分)

树的统计-传送门

题目描述

一棵树上有 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;


}


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值