高级搜索-线段树[C/C++]

news2024/12/27 13:48:35

线段树

文章目录

  • 线段树
    • 前言
    • 一、线段树的定义
    • 二、线段树的结构与建立
      • 2..1 节点定义
      • 2.2 递归建树
      • 2.3 静态数组空间的解释
    • 三、线段树的操作
      • 3.1 单点修改
      • 3.2 单点查询
      • 3.3 区间查询
      • 3.3 区间修改
    • 四、动态开点线段树
      • 递增分配器

前言

对于求数组区间和我们可以处理出前缀和后可以在O(1)的时间内计算出任意区间和,但是前缀和的缺点在于一旦区间发生改变我们就要重新计算前缀和,当频繁进行区间操作时前缀和的优势荡然无存,所以我们需要一种数据结构来解决这类需求。

**线段树(segment tree)**是分治思想在线性数据上的一种应用,通过把一段区间不断分治为小区间来进行建树。

我们把一段区间不断向下二分,会发现整体的结构形似一棵二叉树。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

一、线段树的定义

线段树是基于分治思想的二叉树,用来维护区间信息(区间信息,区间最值,区间gcd等),可以在logn的时间内执行区间修改和区间查询

线段树中每个叶子节点存储元素本身,非叶子节点存储区间内元素的统计值。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

上图就是一棵线段树。

二、线段树的结构与建立

这里选择静态建立

2…1 节点定义

//#define lc p << 1
//#define rc p << 1 | 1
//constexpr int N = 5e5 + 10;
//int n, a[N];
struct Node
{
    int l, r, sum;
} tree[N * 4];//为什么开N*4后面有证明

2.2 递归建树

建树的过程其实就是对一段数组不断往下分治的过程。

建树过程如下:

  1. l,r代表当前节点的区间,p代表节点下标索引
  2. 如果l == r,说明是叶子节点,建立完就return
  3. 否则,将区间[l , r]二分为[l , mid] , [mid + 1 , r]分别建立左右子树
  4. 更新当前节点的sum值
void build(int p, int l, int r)
{
    tree[p] = {l, r, a[l]};
    if (l == r)
        return;
    int m = (l + r) >> 1;
    build(lc, l, m);
    build(rc, m + 1, r);
    tree[p].sum = tree[lc].sum + tree[rc].sum;
}

2.3 静态数组空间的解释

我们为什么开辟4*N个节点呢?

先来看最简单的情况,我们的线段树恰好为一棵满二叉树,那么我们最后一层叶子节点个数即为数组长度n,它的上面所有节点的数目为n - 1,那么最大编号为(n - 1) * 2 + 1

否则,由于每次划分出的两个区间长度最多差1,我们的线段树的非满二叉树情况只可能是一棵满二叉树的若干叶子节点上分出若干二叉

也就是说我们此时n = 2 ^ m + 2 * k(k = 1 , 2 , 3…)

如下图为例,n = 2 ^ m + 2

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

对于非满二叉树的情况,就是在一棵满二叉树的叶子节点层增加若干二叉的情况,我们发现二叉会出现在长度大于1的奇数长度区间下,而且出现位置为左右的偶数编号位置交替出现(如上图如果一个二叉就是在8号,两个就是8号和12号,三个就是8号,10号,12号…)

那么我们最后一层有2个节点,倒数第二层有n - 2个节点,倒数第三层有n - 3个节点,我们的最大编号为(n - 2 - 1 + (n - 2) / 2 + 1) * 2 * 1 + 1 = 3 * n - 5

同样的,当n = 2 ^ m + 2 * k ,(k = 1 , 2 , 3 …)

我们最大编号为(n - 2 * k - 1 + (n - 2k) / 2k * (2k - 1) + 1) * 2 + 1 = (4 - 1/k) * n - (8*k - 3) < 4 * n

故开辟4*n个节点

三、线段树的操作

3.1 单点修改

类似于平衡树的查找,不断缩小查询范围,根据区间往下递归即可

void update(int p, int x, int k) // 在p为根的树中找到x,加上k
{
    if (tree[p].l == x && tree[p].r == x) // 找到了叶子
    {
        tree[p].sum += k;
        return;
    }
    int m = (tree[p].l + tree[p].r) >> 1;
    if (x > m)
        update(rc, x, k);
    else
        update(lc, x, k);
    tree[p].sum = tree[lc].sum + tree[rc].sum;
}

3.2 单点查询

和单点修改差不多。

// 单点查询
int query(int p, int x)
{
    if (tree[p].l == x && tree[p].r == x) // 找到了叶子
        return tree[p].sum;
    if (x <= tree[lc].l)
        return query(lc, x);
    if (x >= tree[rc].l)
        return query(rc, x);
    assert(false);
    return -1;
}

3.3 区间查询

类似于treap的split和merge,例如区间[4 , 9]可以拆成[4 , 5] , [6 , 8] , [9 , 9],合并这三个区间的值就是我们查询的结果。

区间查询过程如下:

  1. 从根节点进入,开始递归查询[x , y]
  2. 如果x <= l <= r <= y,那么我们直接当前节点的结果。
  3. 否则,判断左右区间是否和查询区间有重叠,如果有,递归对应子树
// 区间查询
int query(int p, int x, int y)
{
    if (x <= tree[p].l && tree[p].r <= y)
        return tree[p].sum;
    int m = (tree[p].l + tree[p].r) >> 1, sum = 0;
    if (x <= m)
        sum += query(lc, x, y);
    if (y > m)
        sum += query(rc, x, y);
    return sum;
}

3.3 区间修改

如果我们执行区间长度次单点修改,那么我们的时间复杂度显然太高了,我们这里的处理方法是给

// 向上更新
void pushup(int p)
{
    tree[p].sum = tree[lc].sum + tree[rc].sum;
}
// 向下更新
void pushdown(int p)
{
    if (tree[p].add)
    {
        tree[lc].sum += tree[p].add * (tree[lc].r - tree[lc].l + 1);
        tree[rc].sum += tree[p].add * (tree[rc].r - tree[rc].l + 1);
        tree[lc].add += tree[p].add;
        tree[rc].add += tree[p].add;
        tree[p].add = 0;
    }
}
// 区间修改
void update(int p, int x, int y, int k)
{
    if (x <= tree[p].l && tree[p].r <= y)
    {
        tree[p].sum += k * (tree[p].r - tree[p].l + 1);
        tree[p].add += k;
        return;
    }
    int m = (tree[p].l + tree[p].r) >> 1;
    pushdown(p);
    if (x <= m)
        update(lc, x, y, k);
    if (y > m)
        update(rc, x, y, k);
    pushup(p);
}

四、动态开点线段树

递增分配器

template <class T>
class CachedObj
{
public:
    void *operator new(size_t s)
    {
        if (!head)
        {
            T *a = new T[SIZE];
            for (size_t i = 0; i < SIZE; ++i)
                add(a + i);
        }
        T *p = head;
        head = head->CachedObj<T>::next;
        return p;
    }
    void operator delete(void *p, size_t)
    {
        if (p)
            add(static_cast<T *>(p));
    }
    virtual ~CachedObj() {}

protected:
    T *next;

private:
    static T *head;
    static const size_t SIZE;
    static void add(T *p)
    {
        p->CachedObj<T>::next = head;
        head = p;
    }
};
template <class T>
T *CachedObj<T>::head = 0;
template <class T>
const size_t CachedObj<T>::SIZE = 10000;
class Node : public CachedObj<Node>
{
public:
    Node *left;
    Node *right;
    int add;
    bool v;
};

动态开点线段树模板,其实就是把原来节点里存的区间放到函数参数里面了

#define int long long
int a[500010], n, m;
template <class T>
class CacheObj
{
public:
    void *operator new(size_t)
    {
        if (!head)
        {
            T *a = new T[SIZE];
            for (size_t i = 0; i < SIZE; i++)
                add(a + i);
        }
        T *p = head;
        head = head->CacheObj<T>::next;
        return p;
    }
    void operator delete(void *p, size_t)
    {
        if (p)
            add(static_cast<T *>(p));
    }

    virtual ~CacheObj() {}

protected:
    T *next;

private:
    static T *head;
    static const size_t SIZE;
    static void add(T *p)
    {
        p->CacheObj<T>::next = head;
        head = p;
    }
};
template <class T>
T *CacheObj<T>::head = nullptr;
template <class T>
const size_t CacheObj<T>::SIZE = 10000;
class Node : public CacheObj<Node>
{
public:
    Node() : left(nullptr), right(nullptr), sum(0), add(0) {}
    Node *left;
    Node *right;
    int sum;
    int add;
};
class SegmentTree
{
public:
    SegmentTree() : _root(new Node())
    {
    }
    SegmentTree(int l, int r) : _root(build(new Node(), l, r))
    {
    }
    Node *build(Node *p, int l, int r)
    {
        p->sum = a[l];
        if (l == r)
            return p;
        int mid = (r + l) >> 1;
        p->left = build(new Node(), l, mid);
        p->right = build(new Node(), mid + 1, r);
        pushup(p);
        return p;
    }
    int query(int x)
    {
        return query(x, 1, n, _root);
    }
    int query(int x, int l, int r, Node *node)
    {
        if (l == x && r == x)
            return node->sum;
        int mid = (r + l) >> 1;
        pushdown(node, l, r, mid);

        if (mid >= x)
            return query(x, l, mid, node->left);

        return query(x, mid + 1, r, node->right);
    }
    int query(int left, int right)
    {
        return query(left, right, 1, n, _root);
    }
    int query(int left, int right, int l, int r, Node *node)
    {
        if (left <= l && r <= right)
        {
            return node->sum;
        }
        int mid = (r + l) >> 1, ret = 0;
        pushdown(node, l, r, mid);

        if (mid >= left)
            ret += query(left, right, l, mid, node->left);
        if (mid < right)
            ret += query(left, right, mid + 1, r, node->right);
        return ret;
    }
    void pushup(Node *p)
    {
        p->sum = p->left->sum + p->right->sum;
    }
    void pushdown(Node *p, int l, int r, int mid)
    {
        if (!p->left)
            p->left = new Node();
        if (!p->right)
            p->right = new Node();
        if (p->add)
        {
            p->left->add += p->add;
            p->left->sum += p->add * (mid - l + 1);
            p->right->add += p->add;
            p->right->sum += p->add * (r - mid);
            p->add = 0;
        }
    }
    void update(int x, int k)
    {
        update(x, 1, n, k, _root);
    }
    void update(int x, int l, int r, int k, Node *node)
    {
        if (r == x && l == x)
        {
            node->sum += k;
            node->add += k;
        }
        int mid = (r + l) >> 1;
        if (x <= mid)
            update(x, l, mid, k, node);
        else
            update(x, mid + 1, r, k, node);
    }
    void update(int l, int r, int k)
    {
        update(l, r, k, 1, n, _root);
    }
    void update(int left, int right, int k, int l, int r, Node *node)
    // l , r 是当前节点的区间
    {
        if (left <= l && r <= right)
        {
            node->add += k;
            node->sum += k;
            return;
        }
        int mid = (r + l) >> 1;
        pushdown(node, l, r, mid);

        if (mid >= left)
            update(left, right, k, l, mid, node->left);
        if (mid < right)
            update(left, right, k, mid + 1, r, node->right);
        pushup(node);
    }

private:
    Node *_root;
};

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

京东数据采集接口推荐(京东大数据分析工具)

随着京东电商平台的不断发展&#xff0c;平台中店铺数量也越来越多&#xff0c;对于电商卖家而言&#xff0c;在电商运营过程中如何做好数据分析也越来越重要。而电商运营数据往往多而杂&#xff0c;想要高效的完成电商数据分析&#xff0c;品牌需要借助一些电商数据分析软件。…

Leetcode—1457.二叉树中的伪回文路径【中等】

2023每日刷题&#xff08;四十&#xff09; Leetcode—1457.二叉树中的伪回文路径 实现代码 /*** Definition for a binary tree node.* struct TreeNode {* int val;* struct TreeNode *left;* struct TreeNode *right;* };*/ int record[10] {0};int accumula…

Qt C++中调用python,并将软件打包发布,python含第三方依赖

工作中遇到qt c调用我的python 代码&#xff0c;并且想要一键打包&#xff0c;这里我根据参考的以及个人实践的结果来简单实现一下。 环境&#xff1a;windows系统&#xff0c;QT Creater 4.5&#xff0c; python 3.8&#xff08;anaconda虚拟环境&#xff09; 1. 简单QT调用…

pgz easyexcel如何给excel文件添加自定义属性

免费API方式 直接上传URL,自定义修改Excel 视频演示【内含接口地址】 https://www.ixigua.com/7304510132812153385 前情提示 | 功能说明 多选仅支持微软office、office365系列Excel。因为WPS宏功能需要企业版且付费生成xlsx、xlsm等文件,office和WPS均可以打开,均可以单…

红队攻防实战系列一之Cobalt Strike

他日若遂凌云志&#xff0c;敢笑黄巢不丈夫 本文首发于先知社区&#xff0c;原创作者即是本人 前言 在红队攻防中&#xff0c;需要我们拥有综合能力&#xff0c;不仅仅是web渗透的漏洞挖掘与利用&#xff0c;边界突破的方式有很多种&#xff0c;当然这需要我们拥有很强的意识…

(4)BUUCTF-web-[极客大挑战 2019]EasySQL1

前言&#xff1a; 觉得这个题目挺有意义的&#xff0c;因为最近在学数据库&#xff0c;但是不知道在现实中有什么应用&#xff0c;所以学起来也没有什么兴趣&#xff0c;做了这个题目&#xff0c;发现数据库还是挺有用处的&#xff0c;哈哈 知识点&#xff1a; mysql 中and和…

incast,拥塞控制,内存墙的秘密

数据中心 incast&#xff0c;广域网拥塞&#xff0c;内存墙都是一类问题。 我接触 incast 很久了&#xff0c;大多是帮忙查问题&#xff0c;也解过几例。 我记得有一次在业务几乎总是(在统计学上&#xff0c;几乎和总是属同义强调) tail latency 很大时&#xff0c;我建议在 …

【Python3】【力扣题】349. 两个数组的交集

【力扣题】题目描述&#xff1a; 【Python3】代码&#xff1a; 1、解题思路&#xff1a;集合的交集。两个数组都转为集合&#xff0c;获取集合的交集。 知识点&#xff1a;set(...)&#xff1a;转为集合&#xff0c;集合的元素不重复。 集合1.intersection(集合2)&#xff1a…

将 Hexo 部署到阿里云轻量服务器(保姆级教程)

将 Hexo 部署到阿里云轻量服务器(保姆级教程) 顺哥轻创 1 前言 作为有梦想的,有追求的程序员,有一个自己的个人博客简直就是必须品。你可以选择 wordpress 这种平台,直接使用,在任何地方只要有网络就能写博客。还可以选择 hexo 这种静态博客,但是发文章就没有那么随心…

基于opencv+ImageAI+tensorflow的智能动漫人物识别系统——深度学习算法应用(含python、JS、模型源码)+数据集(三)

目录 前言总体设计系统整体结构图系统流程图 运行环境爬虫模型训练实际应用 模块实现1. 数据准备1&#xff09;爬虫下载原始图片2&#xff09;手动筛选图片 2. 数据处理1&#xff09;切割得到人物脸部2&#xff09;重新命名处理后的图片3&#xff09;添加到数据集 3. 模型训练及…

webpack 打包优化

在vue.config.js中配置 下载 uglifyjs-webpack-plugin 包 const { defineConfig } require("vue/cli-service"); var path require("path");module.exports defineConfig({transpileDependencies: true,filenameHashing: false, // 去除Vue打包后.cs…

SSM手机资讯网站系统开发mysql数据库web结构java编程计算机网页源码eclipse项目

一、源码特点 SSM 手机资讯网站系统是一套完善的信息系统&#xff0c;结合springMVC框架完成本系统&#xff0c;对理解JSP java编程开发语言有帮助系统采用SSM框架&#xff08;MVC模式开发&#xff09;&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模…

PHP 针对mysql 自动生成数据字典

PHP 针对mysql 自动生成数据字典 确保php 可以正常使用mysqli 扩展 这里还需要注意 数据库密码 如果密码中有特殊字符 如&#xff1a; 首先&#xff0c;我们需要了解MySQL中的特殊字符包括哪些。MySQL中的特殊字符主要包括以下几类&#xff1a; 1. 单引号&#xff08;&a…

【开源】基于Vue和SpringBoot的农家乐订餐系统

项目编号&#xff1a; S 043 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S043&#xff0c;文末获取源码。} 项目编号&#xff1a;S043&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 用户2.2 管理员 三、系统展示四、核…

jvm优化之:OOM(out of memory)内存溢出

内存溢出 注意内存溢出不是内存泄漏&#xff01;&#xff01;这里主要是介绍如何用jdk自带的jmap工具导出进程堆空间快照。内存溢出&#xff1a; Out Of Memory&#xff0c;是指申请的堆内存空间不够用了&#xff0c;比如&#xff1a;你申请了10M空间&#xff0c;但是你要放12M…

鸿蒙开发-ArkTS 语言-循环渲染

鸿蒙开发-ArkTS 语言-状态管理 4. 渲染控制 对于 UI 渲染&#xff0c;可以基于数据结构选择一些内置方法&#xff08;例如&#xff1a;ForEach&#xff09;快速渲染 UI 结构。 4.1 if-else条件渲染 ArkTS提供了渲染控制的能力。条件渲染可根据应用的不同状态&#xff0c;使…

ORA-14452: 试图创建, 变更或删除正在使用的临时表中的索引

在编写一个test存储过程中出现一个错误报告:ORA-14452: 试图创建, 变更或删除正在使用的临时表中的索引,代码如下 create or replace PROCEDURE TMP_TRANSCRIPT AS str_sql varchar2(500);v_flag number:0; --标识 begin--判断临时表是否存在SELECT COUNT(*) into v_flag FROM…

【版本管理 | Git 】Git最佳实践系列(一) —— LFS .gitignore 最佳实践,确定不来看看?

&#x1f935;‍♂️ 个人主页: AI_magician &#x1f4e1;主页地址&#xff1a; 作者简介&#xff1a;CSDN内容合伙人&#xff0c;全栈领域优质创作者。 &#x1f468;‍&#x1f4bb;景愿&#xff1a;旨在于能和更多的热爱计算机的伙伴一起成长&#xff01;&#xff01;&…

华大基因基因检测产品发布,助力早发冠心病风险评估

冠状动脉性心脏病&#xff0c;简称冠心病。冠心病作为导致猝死的常见原因之一&#xff0c;近年来备受关注。早发冠心病是指冠心病发病年龄男性≤55岁&#xff0c;女性≤60岁。早发冠心病是一种发病时心肌损伤严重的冠心病&#xff0c;由于心肌缺血&#xff0c;还有可能会导致急…

基于C#实现双端队列

话说有很多数据结构都在玩组合拳&#xff0c;比如说&#xff1a;块状链表&#xff0c;块状数组&#xff0c;当然还有本篇的双端队列&#xff0c;是的&#xff0c;它就是栈和队列的组合体。 一、概念 我们知道普通队列是限制级的一端进&#xff0c;另一端出的 FIFO 形式&#…