第四章 No.2单点线段树的介绍与使用

news2024/11/15 19:42:40

文章目录

    • 基本操作
    • 练习题
      • 1275. 最大数
      • 245. 你能回答这些问题吗
      • 246. 区间最大公约数

基本操作

单点线段树一共4个常用操作,pushup, build, modify, query
相比区间线段树少了pushdown,懒标记,由于pushdown的实现极容易SF,所以能用单点线段树就不用用区间线段树
image.png

单点线段树和区间线段树是我自己的叫法,当线段树只支持修改任意点的操作时,我称它为单点线段树。当线段树支持修改整个区间时,我称它为区间线段树

线段树的空间问题,要开多少空间?若线段长度为 n n n,一般开 4 n 4n 4n的空间
image.png

除了最后一层,剩下的是一个满二叉树

image.png

四个操作介绍:
void pushup(int u):合并线段的操作,根据子节点的信息,维护更新父节点的信息
void build(int u, int l, int r):建立一个 [ l , r ] [l, r] [l,r]区间,u是区间在树中对应的唯一下标
void modify(int u, int x, int v):修改区间 [ x , x ] [x, x] [x,x]的属性为v,其中u是当前区间在树中对应的唯一下标
void query(int u, int l, int r):查询区间 [ l , r ] [l, r] [l,r]在树中的信息
分三种情况:

  1. 当前节点u表示的线段 [ t l , t r ] [tl, tr] [tl,tr]是查询区间 [ l , r ] [l, r] [l,r]的子集,此时直接返回当前区间的信息
  2. 当前节点u表示的线段 [ t l , t r ] [tl, tr] [tl,tr]不是查询区间 [ l , r ] [l, r] [l,r]的子集,但是两者有交集,根据 [ t l , t r ] [tl, tr] [tl,tr]的中点 m i d mid mid可以分成两种情况
  • l < = m i d l <= mid l<=mid,说明查询区间与当前区间的左半段有交集,需要查询 [ t l , m i d ] [tl, mid] [tl,mid],也就是当前节点的左孩子
  • r > m i d r > mid r>mid,说明查询区间与当前区间的右半段有交集,需要查询 [ m i d + 1 , t r ] [mid + 1, tr] [mid+1,tr],也就是当前节点的右孩子

至于说会存在当前区间与查询区间完全没有交集的情况吗?
只要保证查询区间与根节点表示的区间有交集,由于我们每次递归查询的时候,只会选择有交集的区间进行递归,没有交集的区间就不会选择,所以每次判断的时候不会遇到当前区间与查询区间没有交集的情况

当前节点的下标用u表示,那么左孩子的下标为 u < < 1 u << 1 u<<1,右孩子的下标为 u < < 1   ∣   1 u << 1\ |\ 1 u<<1  1

其中需要说明的是build操作,build建立的线段树是静态的,也就是说我们要提前知道线段的长度才能用build开出线段树
build将所有节点的信息初始化为空,再根据题目给定的信息调用modify修改空节点,以此做为线段树的初始化

build操作的实现:

void bulid(int u, int l, int r)
{
	tr[i] = { l, r };
	if (l == r) return;
	int mid = l + r >> 1;
	build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
}

递归实现bulid,第一个参数 u u u作为递归参数,表示节点的编号, l l l r r r表示当前区间的左右端点
build将在树中建立一个 [ l , r ] [l, r] [l,r]区间,若l != r说明当前建立的区间不是树的叶子节点,需要二分 l l l r r r,然后继续往下建立区间。直到l == r停止,当前节点为叶节点,无法继续建立区间

modify操作的实现:

void modify(int u, int x, int v)
{
	if (tr[u].l == x && tr[u].r == x) tr[u].v = v;
	else
	{
		int mid = tr[u].l + tr[u].r >> 1;
		if (x <= mid) modify(u << 1, x, v);
		else modify(u << 1 | 1, x, v);
		pushup(u); // 节点信息的维护
	}
}

同样,u是递归参数,x为需要修改的区间。由于是单点线段树,所以只支持修改“一个点”的操作,即l == r == x
若当前节点u表示的区间不是 [ x , x ] [x, x] [x,x],那么二分区间向下递归查找目标区间。注意,修改完节点信息后,需要进行pushup维护,向上修改所有包含x的区间信息

query操作的实现:

int query(int u, int l, int r)
{
	if (l <= tr[u].l && tr[u].r <= r) return tr[u].v;
	int mid = trr[u].l + tr[u].r >> 1;
	int v = 0;
	if (l <= mid) v = query(u << 1, l, r);
	if (r > mid) v = max(v, query(u << 1 | 1, l, r));
	return v;
}

同样,u是递归参数,query将查找区间 [ l , r ] [l, r] [l,r]的信息,之前已经介绍过query的三种情况,这里不再赘述

pushup操作需要结合题目具体实现


练习题

1275. 最大数

1275. 最大数 - AcWing题库
image.png

板子题,题意很直白,需要实现两个操作:1. 在任意位置(序列最后)添加一个数,对应modify操作 2. 询问某段区间的最大值,对应query操作
思考节点需要维护什么信息才能支持query(区间最大值)操作?很显然,当前区间 [ l , r ] [l, r] [l,r]的最大值一定要维护。那么 [ l , r ] [l, r] [l,r]的最大值是否能通过子区间的信息推导出来,显然一个max就能搞定

所以一顿分析后,我们知道节点只需要存储 [ l , r ] [l, r] [l,r]与最大值即可,pushup将基于两子区间推导当前区间的最大值

#include <iostream>
using namespace std;

typedef long long LL;
const int N = 2e5 + 10;
struct Node
{
    int l, r;
    int v; // 表示当前区间的最大值
}tr[4 * N];

void pushup(int u)
{
    tr[u].v = max(tr[u << 1].v, tr[u << 1 | 1].v);
}

void build(int u, int l, int r)
{
    tr[u] = { l, r };
    if (l == r) return;
    int mid = (l + r) >> 1;
    build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
}

int query(int u, int l, int r)
{
    if (l <= tr[u].l && tr[u].r <= r) return tr[u].v;
    int mid = (tr[u].l + tr[u].r) >> 1;
    int v = 0;
    if (l <= mid) v = query(u << 1, l, r);
    if (r > mid) v = max(v, query(u << 1 | 1, l, r));
    return v;
}

void modify(int u, int x, int v)
{
    if (tr[u].l == x && tr[u].r == x) tr[u].v = v;
    else
    {
        int mid = (tr[u].l + tr[u].r) >> 1;
        if (x <= mid) modify(u << 1, x, v);
        else modify(u << 1 | 1, x, v);
        pushup(u);
    }
}

int main()
{
    int m, p;
    scanf("%d%d", &m, &p);
    int n = 0; // 表示线段的数量
    build(1, 1, m);
    int a = 0, x;
    char op[2];
    while (m -- )
    {
        scanf("%s%d", op, &x);
        if (op[0] == 'A')
            modify(1, ++ n, ((LL)a + x) % p);
        else
        {
            a = query(1, n - x + 1, n);
            printf("%d\n", a);
        }
    }
    return 0;
}

debug:query的int v = 0必须初始化
因为第一个if不一定进去,v的值不一定能被该if初始化
所以在第二个if时,v的随机值可能影响最后的max取值


245. 你能回答这些问题吗

245. 你能回答这些问题吗 - AcWing题库
image.png

裸题,实现两个操作:1. 单点修改 2. 区间的最大连续子段和
一般问题的最大连续子段和可以用dp与类似归并排序的递归解,显然这题不能用dp,所以这里借用归并的思想
将当前区间 [ l , r ] [l, r] [l,r]分成两个子区间,当前区间的最大连续子段和有三种情况

  1. 左子区间的最大连续子段和
  2. 右子区间的最大连续子段和
  3. 左子区间的最大后缀和加上右子区间的最大前缀和

在三者中取max即可。所以线段树的节点需要维护三个信息:1. 最大连续子段和 2. 最大前缀和 3. 最大后缀和
当前区间的前缀和要如何维护?有两种情况:1. 左子区间的最大前缀和 2. 左子区间和加上右子区间的最大前缀和,当前区间的最大后缀和也是同理,所以节点还需要维护当前区间和这一信息

#include <iostream>
using namespace std;

typedef long long LL;
const int N = 5e5 + 10;
int a[N];
struct Node
{
    int l, r;
    LL sum, lm, rm, tm;
}tr[4 * N];

void pushup(Node &u, Node &l, Node &r)
{
    u.sum = l.sum + r.sum;
    u.lm = max(l.lm, l.sum + r.lm);
    u.rm = max(r.rm, r.sum + l.rm);
    u.tm = max(max(l.tm, r.tm), l.rm + r.lm);
}

void pushup(int u)
{
    pushup(tr[u], tr[u << 1], tr[u << 1 | 1]);
}

void build(int u, int l, int r)
{
    if (l == r) tr[u] = { l, r, a[l], a[l], a[l], a[l] };
    else
    {
        tr[u] = { l, r };
        int mid = l + r >> 1;
        build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
        pushup(tr[u], tr[u << 1], tr[u << 1 | 1]);
    }
}

void modify(int u, int x, LL v)
{
    if (tr[u].l == x && tr[u].r == x) tr[u] = { x, x, v, v, v, v };
    else
    {
        int mid = tr[u].l + tr[u].r >> 1;
        if (x <= mid) modify(u << 1, x, v);
        else modify(u << 1 | 1, x, v);
        pushup(u);
    }
}

Node query(int u, int l, int r)
{
    if (l <= tr[u].l && tr[u].r <= r) return tr[u];
    else
    {
        int mid = tr[u].l + tr[u].r >> 1;
        if (r <= mid) return query(u << 1, l, r);
        else if (l > mid) return query(u << 1 | 1, l, r);
        else
        {
            Node res;
            Node left = query(u << 1, l, r);
            Node right = query(u << 1 | 1, l, r);
            pushup(res, left, right);
            return res;
        }
    }
}

int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++ i ) scanf("%d", &a[i]);
    build(1, 1, n);
    int t, x, y;
    while (m -- )
    {
        scanf("%d%d%d", &t, &x, &y);
        if (t == 1)
        {
            if (x > y) swap(x, y);
            printf("%lld\n", query(1, x, y).tm);
        }
        else
        {
            modify(1, x, y);
        }
    }
    return 0;
}

其中,pushup被设置为重载,这是因为有些函数会使用pushup(int u),有些函数会使用pushup(Node &u, Node &l, Node &r)
与第一题的板子不同,这一题的板子中。build函数使用了pushup的第一个重载,因为题目已经给定了指定的数用来初始化线段树,而第一题没有给定数据,所以只能先建空树

query也使用了pushup,并且else部分与第一题不同
由于第一题查询的是区间最大值,所以二分当前区间后,查出左子区间的最大值与右子区间的最大值再取个max即可
但是这题要查询的是最大连续子段和,不能从左右子区间的最大连续子段和推出当前区间的最大连续字段和,因为还有一种情况存在:左子区间的最大后缀+右子区间的最大前缀
所以这里要判断三种情况:

  1. r <= mid,查询区间 [ l , r ] [l, r] [l,r]完全在当前区间的左子区间
  2. l > mid,查询区间 [ l , r ] [l, r] [l,r]完全在当前区间的右子区间
  3. 查询区间 [ l , r ] [l, r] [l,r]不仅在当前区间的左子区间还在当前区间的右子区间,此时要调用pushup,整合两个子区间的信息,将这些信息保存到Node中

246. 区间最大公约数

246. 区间最大公约数 - AcWing题库
image.png

整个区间加上或者减去一个数,不涉及“修改整个区间的值”,自然想到差分数组,可以用节点维护差分信息,即 a [ i ] − a [ i − 1 ] a[i] - a[i-1] a[i]a[i1]
由于要求区间的最大公约数,所以至少要维护”区间的最大公约数“的信息
接着思考区间 [ l , r ] [l, r] [l,r]的最大公约数是否能通过子区间的最大公约数得到?若是不能则需要维护其他信息
假设当前区间被分为左右两个区间,左区间最大公约数为 a a a,右区间最大公约数为 b b b
显然,由于求当前区间的最大公约数只会用到 a a a b b b,所以节点就不用额外维护其他信息

当区间维护着差分信息,如何求其的最大公约数?
image.png
由性质d | a && d | b -> d | ax + by,所以
a 1 , a 2 , . . . , a n a_1, a_2, ..., a_n a1,a2,...,an的最大公约数,等同于求 a 1 , a 2 − a 1 , . . . , a n − a n − 1 a_1, a_2-a_1, ..., a_n-a_{n-1} a1,a2a1,...,anan1的最大公约数
后n-1个数是节点保存的差分信息,求这些数: a 2 − a 1 , . . . , a n − a n − 1 a_2-a_1, ..., a_n-a_{n-1} a2a1,...,anan1的最大公约数,可以直接用节点维护的差分信息
但是第1个数是个前缀和信息,节点只保存差分信息不够,还要维护sum信息以求前缀和得到区间的第一个数 a l a_l al

#include <iostream>
using namespace std;

typedef long long LL;
const int N = 5e5 + 10;

LL w[N];

struct Node
{
    int l, r;
    LL d, sum;
}tr[N * 4];

LL gcd(LL a, LL b)
{
    return b ? gcd(b, a % b) : a;
}

void pushup(Node &u, Node &l, Node &r)
{
    u.d = gcd(l.d, r.d);
    u.sum = l.sum + r.sum;
}

void pushup(int u)
{
    pushup(tr[u], tr[u << 1], tr[u << 1 | 1]);
}

void build(int u, int l, int r)
{
    if (l== r) 
    {
        LL t = w[l] - w[l - 1];
        tr[u] = { l, r, t, t };
    }
    else
    {
        tr[u] = { l, r };
        int mid = l + r >> 1;
        build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
        pushup(u);
    }
}

void modify(int u, int x, LL v)
{
    if (tr[u].l == x && tr[u].r == x) tr[u].sum += v, tr[u].d += v;
    else
    {
        int mid = tr[u].l + tr[u].r >> 1;
        if (x <= mid) modify(u << 1, x, v);
        else modify(u << 1 | 1, x, v);
        pushup(u);
    }
}

Node query(int u, int l, int r)
{
    if (l <= tr[u].l && tr[u].r <= r) return tr[u];
    else
    {
        int mid = tr[u].l + tr[u].r >> 1;
        if (r <= mid) return query(u << 1, l, r);
        else if (l > mid) return query(u << 1 | 1, l, r);
        else
        {
            auto left = query(u << 1, l, r);
            auto right = query(u << 1 | 1, l, r);
            Node res;
            pushup(res, left, right);
            return res;
        }
    }
}

int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++ i ) scanf("%lld", &w[i]);
    build(1, 1, n);
    
    char op[2]; int l, r;
    LL t;
    while (m -- )
    {
        scanf("%s%d%d", op, &l, &r);
        if (op[0] == 'Q')
        {
            if (l == r) printf("%lld\n", query(1, 1, l).sum);
            else
            {
                LL a = query(1, 1, l).sum;
                printf("%lld\n", abs(gcd(a, query(1, l + 1, r).d)));
            }
        }
        else
        {
            scanf("%lld", &t);
            modify(1, l, t);
            if (r + 1 <= n) modify(1, r + 1, -t);
        }
    }
    return 0;
}

debug:if (l == r) printf("%lld\n", query(1, 1, l).sum)当查询单点最大公约数时,需要返回这个点上的数,由于保存的时差分信息所以需要求一个前缀和
之前写成if (l == r) printf("%lld\n", query(1, l, r).d),乐
题目没想清楚就写题,真的会debug到死

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

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

相关文章

Python GUI应用程序开发之wxPython库详解

概要 wxPython是一个强大的跨平台GUI工具包&#xff0c;它使用Python编程语言开发&#xff0c;提供了丰富的控件功能。如果你是一名Python开发者&#xff0c;而且希望创建一个功能齐全的桌面应用程序&#xff0c;那么wxPython是一个值得考虑的选择。wxPython是wxWidgets C库的P…

算法——十大排序 (部分未完结)

总结 为什么需要稳定排序&#xff1f; ▪ 让第⼀个关键字的排序结果服务于第⼆个关键字排序中数值相同的那些数 ▪ 主要是为了第⼀次考试分数相同时候&#xff0c;可以按照第⼆次分数的⾼低进行排序 一、冒泡排序 从最简单的冒泡排序开始 思想&#xff1a;交换相邻的元素&am…

电子文件管理系统的最佳实践指南分享

电子文件管理系统是一种专门用于管理电子文件的软件工具&#xff0c;可以帮助组织更有效地管理、存储、检索和共享文件。 首先&#xff0c;在选择适合自己组织的电子文件管理系统时&#xff0c;需要考虑以下几个关键因素。首先&#xff0c;系统的易用性和用户界面是否友好&…

Qt应用开发(基础篇)——布局管理Layout Management

目录 一、前言 二&#xff1a;相关类 三、水平、垂直、网格和表单布局 四、尺寸策略 一、前言 在实际项目开发中&#xff0c;经常需要使用到布局&#xff0c;让控件自动排列&#xff0c;不仅节省控件还易于管控。Qt布局系统提供了一种简单而强大的方式来自动布局小部件中的…

前段时间面试了一些人,有这些槽点跟大家说说

大家好&#xff0c;我是拭心。 前段时间组里有岗位招人&#xff0c;花了些时间面试&#xff0c;趁着周末把过程中的感悟和槽点总结成文和大家讲讲。 简历书写和自我介绍 今年的竞争很激烈&#xff1a;找工作的人数量比去年多、平均质量比去年高。裸辞的慎重&#xff0c;要做好…

Android 第三方库CalendarView

Android 第三方库CalendarView 根据需求和库的使用方式&#xff0c;自己弄了一个合适自己的日历&#xff0c;仅记录下&#xff0c;方便下次弄其他样式的日历。地址 需求&#xff1a; 只显示当月的数据 默认的月视图有矩形的线 选中的天数也要有选中的矩形框 今天的item需要…

强推!大语言模型『百宝书』,一文缕清所有大模型!

夕小瑶科技说 原创 作者 | 王思若 最近&#xff0c;大型语言模型无疑是AI社区关注的焦点&#xff0c;各大科技公司和研究机构发布的大模型如同过江之鲫&#xff0c;层出不穷又眼花缭乱。 让笔者恍惚间似乎又回到了2020年国内大模型“军备竞赛”的元年&#xff0c;不过那时候…

package-lock.json 作用

参照&#xff1a; https://www.cnblogs.com/honkerzh/p/16767566.html

【雕爷学编程】MicroPython动手做(25)——语音合成与语音识别

知识点&#xff1a;什么是掌控板&#xff1f; 掌控板是一块普及STEAM创客教育、人工智能教育、机器人编程教育的开源智能硬件。它集成ESP-32高性能双核芯片&#xff0c;支持WiFi和蓝牙双模通信&#xff0c;可作为物联网节点&#xff0c;实现物联网应用。同时掌控板上集成了OLED…

山西电力市场日前价格预测【2023-08-01】

日前价格预测 预测明日&#xff08;2023-08-01&#xff09;山西电力市场全天平均日前电价为310.15元/MWh。其中&#xff0c;最高日前电价为335.18元/MWh&#xff0c;预计出现在19: 45。最低日前电价为288.85元/MWh&#xff0c;预计出现在14: 00。 价差方向预测 1&#xff1a;实…

无涯教程-jQuery - css( properties )方法函数

css(properties)方法将键/值对象设置为所有匹配元素的样式属性。 css( properties ) - 语法 selector.css( properties ) 上面的语法可以写成如下- selector.css( {key1:val1, key2:val2....keyN:valN}) 这是此方法使用的所有参数的描述- key:value - 设置为样式属…

郑州https数字证书

很多注重隐私的网站都注重网站信息的安全&#xff0c;比如购物网站就需要对客户的账户信息以及支付信息进行安全保护&#xff0c;否则信息泄露&#xff0c;客户与网站都有损失&#xff0c;网站也会因此流失大量客户。而网站使用https证书为客户端与服务器之间传输的信息加了一个…

<Git>版本控制工具Git常见的开发操作

下载安装,环境变量配置直接百度; 1.代码拉取: 操作步骤&#xff1a;在正确配置完git的条件下:在本地文件夹下&#xff1a;右键–Git Bash -Here&#xff1a; 出现如下弹窗: 在黑窗口输入代码拉取路径(一般都是把命令和路径直接在外面写好,直接粘贴(在窗口右键,Paste)) 代码拉去…

JavaScript学习 -- 对称加密算法3DES

在现代的互联网时代&#xff0c;数据安全性备受关注。为了保护敏感数据的机密性&#xff0c;对称加密算法是一种常用的方法。在JavaScript中&#xff0c;3DES&#xff08;Triple Data Encryption Standard&#xff09;是一种常用的对称加密算法。本篇博客将为您展示如何在JavaS…

竞速榜实时离线对数方案演进介绍 | 京东云技术团队

一、背景 竞速榜是大促期间各采销群提供的基于京东实时销售数据的排行榜&#xff0c;同样应对大促流量洪峰场景&#xff0c;通过榜单撬动品牌在京东增加资源投入。竞速榜基于用户配置规则进行实时数据计算&#xff0c;榜单排名在大促期间实时变化&#xff0c;相关排名数据在微…

Chrome浏览器中的vue插件devtools的下载方式(使用Chrome应用商店/科学上网情况下)

目录 devtools对前端来说的好处——开发预览、远程调试、性能调优、Bug跟踪、断点调试等 下载步骤&#xff1a; 测试阶段&#xff1a; 最近做项目要使用devtools这个vue插件。 devtools对前端来说的好处——开发预览、远程调试、性能调优、Bug跟踪、断点调试等 下载步骤…

灭蚊灯上架亚马逊美国站UL1559测试报告办理

近年来&#xff0c;随着全球气候变暖和环境变化&#xff0c;蚊虫成为了世界各地人们的头疼问题。为了解决这一困扰&#xff0c;我司研发出一款创新的昆虫控制设备——灭蚊灯&#xff0c;并成功将其上架亚马逊美国站。为了满足亚马逊站对产品的要求&#xff0c;我们积极办理了UL…

寒假作业(蓝桥杯2016年省赛C++A组第6题 )

题目: 注:蓝桥杯2016年省赛C++A组第6题 请填写表示方案数目的整数。 题解: 由题可知这是一道全排列问题,因此我们可以使用c++的next_permutation函数对于1-13的数字进行全排列即可,并每次排列判断是否满足题意。 注意:你提交的应该是一个整数,不要填写任何多余的内…

测试|Selenium介绍及环境搭建

测试|Selenium介绍及环境搭建 1.Selenium是什么 Selenium是用来做web网站 UI自动化的测试工具/测试框架。 我们这里说的Selenium是Selenium2.0&#xff0c;它由Selenium IDE&#xff0c;Webdriver, Selenium Grid组成。 Selenium IDE是用于Selenium测试的完成集成开发环境&…

小程序反馈与投诉混淆官方修改指引

根据《微信小程序平台运营规范》5.14“混淆行为&#xff1a;5.14.1 恶意混淆腾讯官方功能”&#xff0c;相关违规类型包括但不限于以下类型。 官方“反馈与投诉”入口与样式&#xff1a; 违规类型1&#xff1a;混淆官方投诉入口 小程序中的投诉入口样式与官方投诉入口icon名称…