AcWing 2568:树链剖分 ← 线段树+DFS

【题目来源】
https://www.acwing.com/problem/content/2570/

【题目描述】
给定一棵树,树中包含 n 个节点(编号 1∼n),其中第 i 个节点的权值为 ai。
初始时,1 号节点为树的根节点。
现在要对该树进行 m 次操作,操作分为以下 4 种类型:
● 1 u v k,修改路径上节点权值,将节点 u 和节点 v 之间路径上的所有节点(包括这两个节点)的权值增加 k。
● 2 u k,修改子树上节点权值,将以节点 u 为根的子树上的所有节点的权值增加 k。
● 3 u v,询问路径,询问节点 u 和节点 v 之间路径上的所有节点(包括这两个节点)的权值和。
● 4 u,询问子树,询问以节点 u 为根的子树上的所有节点的权值和。

【输入格式】
第一行包含一个整数 n,表示节点个数。
第二行包含 n 个整数,其中第 i 个整数表示 ai。
接下来 n−1 行,每行包含两个整数 x,y,表示节点 x 和节点 y 之间存在一条边。
再一行包含一个整数 m,表示操作次数。
接下来 m 行,每行包含一个操作,格式如题目所述。

【输出格式】
对于每个操作 3 和操作 4,输出一行一个整数表示答案。

【数据范围】
1≤n,m≤10^5,
0≤ai,k≤10^5,
1≤u,v,x,y≤n

【输入样例】
5
1 3 7 4 5
1 3
1 4
1 5
2 3
5
1 3 4 3
3 5 4
1 3 5 10
2 3 5
4 1

【输出样例】
16
69

【算法分析】
● 树链剖分
(1)树链剖分的核心思想,就是将树中任意一条路径拆分成 O(log_2 n) 段的
连续区间(或重链)。拆分后,树的所有操作都可转化为区间操作,且通常采用线段树进行维护。
(2)树链剖分的几个概念

重儿子/轻儿子:结点个数最多的子树的根结点称为当前结点的重儿子,其他子结点称为当前结点的轻儿子。若当前结点存在多个结点个数相同的子树,则任选一个子树的根结点作为当前结点的重儿子。故易知每个结点的重儿子是唯一的
重边/轻边:重儿子与父结点之间的边,称为重边。其他边称为轻边
重链:重边构成的极大路径,称为重链。
DFS序:深度优先遍历树的重儿子,可保证树中各条重链结点的编号是连续的。此性质保证了树链剖分后各区间是连续的。
(3)将一条路径拆分为重链的过程,类似于求最近公共祖先(LCA)。

【算法代码】

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;

const int maxn=1e5+5;
const int maxm=maxn<<1;
int val[maxn],e[maxm],ne[maxm],h[maxn],idx;

//id[]:dfn sequence number of the node
//nv[]:point weight of each dfs sequence number
int id[maxn],nv[maxn],tot;

//cnt[]:number of child nodes, top[]:vertex of heavy chain
//son[]:heavy son, fa[]:parent node
int dep[maxn],cnt[maxn],top[maxn],fa[maxn],son[maxn];

int n,m;

struct SegmentTree {
    int le,ri;
    LL sum,lazy;
} tr[maxn<<2];

void add(int a,int b) {
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}

void dfs1(int u,int father,int depth) { //dfs1:pretreatment
    dep[u]=depth,fa[u]=father,cnt[u]=1;
    for(int i=h[u]; i!=-1; i=ne[i]) {
        int j=e[i];
        if(j==father) continue;
        dfs1(j,u,depth+1);
        cnt[u]+=cnt[j];
        if(cnt[son[u]]<cnt[j]) son[u]=j; //heavy son
    }
}

void dfs2(int u,int vx) { //dfs2:split, t:vertex of heavy chain
    id[u]=++tot,nv[tot]=val[u],top[u]=vx;
    if(!son[u]) return; //leaf node
    dfs2(son[u], vx); //Heavy son's heavy chain split

    for(int i=h[u]; i!=-1; i=ne[i]) { //handle light son
        int j=e[i];
        if(j==fa[u] || j==son[u]) continue;
        dfs2(j,j); //The vertex of the light son's heavy chain is himself
    }
}

/*------------ Content of segment tree ------------*/

void pushup(int u) {
    tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum;
}

void pushdown(int u) {
    auto &rt=tr[u], &L=tr[u<<1], &R=tr[u<<1|1];
    if(rt.lazy) {
        L.sum+=rt.lazy*(L.ri-L.le+1);
        L.lazy+=rt.lazy;
        R.sum+=rt.lazy*(R.ri-R.le+1);
        R.lazy+=rt.lazy;
        rt.lazy=0;
    }
}

void build(int u,int le,int ri) {
    tr[u]= {le,ri,nv[ri],0};
    if(le==ri) return;
    int mid=le+ri>>1;
    build(u<<1,le,mid), build(u<<1|1,mid+1,ri);
    pushup(u);
}

void update(int u,int le,int ri,int k) {
    if(le<=tr[u].le && ri>=tr[u].ri) {
        tr[u].lazy+=k;
        tr[u].sum+=k*(tr[u].ri-tr[u].le+1);
        return;
    }
    pushdown(u);
    int mid=tr[u].le+tr[u].ri>>1;
    if(le<=mid) update(u<<1,le,ri,k);
    if(ri>mid) update(u<<1|1,le,ri,k);
    pushup(u);
}

LL query(int u,int le,int ri) {
    if(le<=tr[u].le && ri>=tr[u].ri) return tr[u].sum;
    pushdown(u);
    int mid=(tr[u].le+tr[u].ri)>>1;
    LL res=0;
    if(le<=mid) res+=query(u<<1,le,ri);
    if(ri>mid) res+=query(u<<1|1,le,ri);
    return res;
}

void update_path(int u,int v,int k) {
    while(top[u]!=top[v]) { //Climb up to find the same heavy chain
        if(dep[top[u]]<dep[top[v]]) swap(u,v);
        //Because of dfs order, the id of the upper node
        //must be smaller than that of the lower node
        update(1,id[top[u]],id[u],k);
        u=fa[top[u]];
    }
    if(dep[u]<dep[v]) swap(u,v);

    //In the same heavy chain, the remaining intervals are processed
    update(1,id[v],id[u],k);
}

LL query_path(int u,int v) {
    LL res=0;
    while(top[u]!=top[v]) { //Climb up to find the same heavy chain
        if(dep[top[u]]<dep[top[v]]) swap(u,v);
        res+=query(1,id[top[u]],id[u]);
        u=fa[top[u]];
    }
    if(dep[u]<dep[v]) swap(u,v);

    //In the same heavy chain, the remaining intervals are processed
    res+=query(1,id[v],id[u]);
    return res;
}

void update_tree(int u,int k) { //Add k to all the subtrees
    //Because of the order of dfs, the interval can be found directly
    //by the number of subtree nodes
    update(1,id[u],id[u]+cnt[u]-1,k);
}

LL query_tree(int u) {
    //Because of the order of dfs, the interval can be found directly
    //by the number of subtree nodes
    return query(1,id[u],id[u]+cnt[u]-1);
}

int main() {
    scanf("%d",&n);
    memset(h,-1,sizeof h);
    for(int i=1; i<=n; i++) scanf("%d",&val[i]);
    for(int i=1; i<n; i++) {
        int a,b;
        scanf("%d %d",&a,&b);
        add(a,b),add(b,a);
    }
    dfs1(1,-1,1);
    dfs2(1,1);
    build(1,1,n);

    scanf("%d",&m);
    while(m--) {
        int op,u,v,k;
        scanf("%d%d",&op,&u);
        if(op==1) {
            scanf("%d%d",&v,&k);
            update_path(u,v,k);
        } else if(op==2) {
            scanf("%d",&k);
            update_tree(u,k);
        } else if(op==3) {
            scanf("%d",&v);
            printf("%lld\n",query_path(u,v));
        } else printf("%lld\n",query_tree(u));
    }

    return 0;
}

/*
in:
5
1 3 7 4 5
1 3
1 4
1 5
2 3
5
1 3 4 3
3 5 4
1 3 5 10
2 3 5
4 1

out:
16
69
*/




【参考文献】
https://www.acwing.com/solution/content/62664/
https://www.acwing.com/problem/content/video/2570/
https://blog.csdn.net/qq_46105170/article/details/125497358








 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/656080.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

力扣225. 用队列实现栈

Problem: 225. 用队列实现栈 文章目录 题目描述&#xff1a;思路Code 题目描述&#xff1a; 思路 1.对一个queue模拟栈的操作&#xff0c;同时用一个int类型的变量topElem记录每次每次队列队尾的元素&#xff08;也即是模拟stack中的stack的栈顶元素&#xff09;&#xff1b; 2…

Linux之单机项目部署

1、虚拟机&#xff08;VMware&#xff09;创建Linux系统 1.1、创建虚拟机 1.2、配置虚拟机IOS映射文件 1.3、虚拟机内部相关配置 等待加载即可&#xff0c;加载完后会弹出图形化界面&#xff0c;如图&#xff1a; 注意&#xff1a;一般我们做为管理员使用ROOT账号来操作&#x…

Tomcat端口配置

Tomcat是开源免费的服务器&#xff0c;其默认的端口为8080&#xff0c;本文讲述一下如何配置端口。 最后在浏览器中输入localhost:8888即可打开Tomcat界面

怎样打造一份个性化画册呢?我来教你

在这个数字化的时代&#xff0c;传统的照片已经不能满足我们对个性化回忆的需求。个性化画册&#xff0c;不仅能够承载我们的记忆&#xff0c;还能展现自我风格。今天&#xff0c;就让我来教你如何打造一份属于自己的个性化画册。 1.要制作电子杂志,首先需要选择一款适合自己的…

IT人的拖延——一放松就停不下来,耽误事?

拖延的表现 在我们的日常工作中&#xff0c;经常会面对这样一种情况&#xff1a;因为要做的Sprint ticket比较复杂或者长时间的集中注意力后&#xff0c;本来打算休息放松一下&#xff0c;刷刷剧&#xff0c;玩玩下游戏&#xff0c;但却一个不小心&#xff0c;没控制住时间&am…

【全开源】JAVA同城搬家系统源码小程序APP源码

JAVA同城搬家系统源码 特色功能&#xff1a; 强大的数据处理能力&#xff1a;JAVA提供了丰富的数据结构和算法&#xff0c;以及强大的并发处理能力&#xff0c;使得系统能够快速地处理大量的货物信息、司机信息、订单信息等&#xff0c;满足大规模物流的需求。智能路径规划&a…

常见5大开发进度盲点问题及解决方案

在软件开发项目中&#xff0c;识别并解决常见的进度管理盲点问题&#xff0c;对于确保项目按时、按预算、高质量完成至关重要。它直接关系到项目能否顺利进行&#xff0c;忽视任何一个问题&#xff0c;都可能导致项目延期、成本超支、质量下降&#xff0c;甚至项目失败。 因此&…

@ConfigurationProperties结合Nacos配置动态刷新之底层原理分析

Hello&#xff0c;我是大都督周瑜&#xff0c;本文给大家分析一下ConfigurationProperties结合Nacos配置动态刷新的底层原理&#xff0c;记得点赞、关注、分享哦&#xff01; 公众号&#xff1a;IT周瑜 应用背景 假如在Nacos中有Data ID为common.yml的配置项&#xff1a; m…

数智赋能内涝治理,四信城市排水防涝解决方案保障城市安全运行

由强降雨、台风造成城市低洼处出现大量积水、内涝的情况时有发生&#xff0c;给人们出行带来了极大不便和安全隐患&#xff0c;甚至危及群众生命财产安全。 为降低内涝造成的损失&#xff0c;一方面我们要大力加强城市排水基础设施的建设&#xff1b;另一方面要全面掌握城市内涝…

目标检测基础初步学习

目标检测&#xff08;Object Detection&#xff09; 目标检测任务说明 在动手学习深度学习中对目标检测任务有如下的描述。 图像分类任务中&#xff0c;我们假设图像中只有一个主要物体对象&#xff0c;我们只关注如何识别其类别。 然而&#xff0c;很多时候图像里有多个我们…

装饰模式:鸡腿堡

文章目录 UML类图目录结构Humburger.javaChickenBurger.javaCondiment.javaChuilli.javaLettuce.javaTest.java深度理解test怎么写 UML类图 目录结构 我们从指向最多的开始写 Humburger.java package zsms;public abstract class Humburger {protected String name;public S…

揭秘Tensor Core黑科技:如何让AI计算速度飞跃

揭秘 Tensor Core 底层&#xff1a;如何让AI计算速度飞跃 Tensor Core&#xff0c;加速深度学习计算的利器&#xff0c;专用于高效执行深度神经网络中的矩阵乘法和卷积运算&#xff0c;提升计算效率。 Tensor Core凭借混合精度计算与张量核心操作&#xff0c;大幅加速深度学习…

redis基本数据结构与应用

文章目录 概要String结构Hash结构List结构Set结构Zset结构bitmap位图类型geo地理位置类型其他常用命令 概要 redis常用的5种不同数据结构类型之间的映射如下&#xff1a; 结构类型结构存储的值结构的读写能力STRING可以是字符串、整数或者浮点数key-value形式&#xff1b;对整…

推荐系统学习笔记(四)--基于向量的召回

离散特征处理 离散特征&#xff1a;性别&#xff0c;国籍&#xff0c;英文单词&#xff0c;物品id&#xff0c;用户id 处理&#xff1a; 建立字典&#xff1a;eg&#xff1a;china 1 向量化&#xff1a;eg&#xff1a;one-hot /embedding&#xff08;低维稠密向量&#xf…

【漏洞复现】用友NC registerServlet JNDI 远程代码执行漏洞(XVE-2024-10248)

0x01 产品简介 用友NC是 用友软件股份有限公司开发的一套企业级管理软件系统。它是一个基于互联网的多层应用系统&#xff0c;旨在为中大型企业提供全面、集成的管理解决方案。是一种商业级的企业资源规划云平台&#xff0c;为企业提供全面的管理解决方案&#xff0c;包括财务…

数组-类似斐波那契数列,给出第一个和第二个结点值,求第n个值

一、问题描述 二、解题方法 可以采用两种方式&#xff1a; 方式1.使用递归&#xff0c;f(n)f(n-1)f(n-2); 当n1时&#xff0c;返回first&#xff1b;当n2时&#xff0c;返回second&#xff1b; 方式2.从第3个结点开始计算&#xff0c;当计算到第n个结点值的时候结束并返回计…

nginx编译安装手把手教学

编译安装nginx的第一步需要从nginx的官网找到nginx最新的稳定版本 下面这是官方网站的资源下载地址 https://nginx.org/en/download.html选中稳定版本点击右键——选择复制链接 在终端内使用wget指令官网下载地址&#xff0c;将nginx下载 使用wget指令下载 wget https://ng…

微服务项目搭建之技术选型

1、什么是微服务 Java微服务是一种架构风格&#xff0c;通过将单个Spring Boot应用程序拆分为一组小型、独立的Spring Boot服务来构建分布式系统。每个微服务都运行在自己的进程中&#xff0c;并使用轻量级通信机制&#xff08;如HTTP或消息队列&#xff09;来进行相互之间的通…

查询DQL

016条件查询之等量关系 条件查询语法格式 select ... from... where过滤条件;等于 select empno, ename from emp where sal3000;select job, sal from emp where enameFORD;select grade, losal, hisal from salgrade where grade 1;不等于 <> 或 ! selectempno,en…

你的手机是如何控制你的手表之广播篇

前言 要让手机能够控制手表&#xff0c;第一步当然要让手机能够“看见”手表&#xff0c;人类作为上帝视角&#xff0c;我们是能够通过眼睛直接看见手机和手表的&#xff0c;但要让手机“看见”手表&#xff0c;就需要一端把自己的信息通过电磁波的形式发往空中&#xff0c;另…