线段树的感悟 : 学过的东西一定要多回头看看,不然真的会忘个干干净净。

线段树的 Introduction :

English Name : Segment Tree
顾名思义 : 该数据结构由两个重要的东西组成 : 线段,树,连起来就是在树上的线段。
想一下,线段有啥特征 ?
不就是两个端点中间一条线吗,哈哈,也可以这么理解,但这样是不是稍微难听呀,所以
我们用一个华丽的词语来描述这个两点之间的一条线,这个词语就是不知道哪个先知发
明的,就是 -- 区间。
所以我们就可猜想到,所以线段树一定是用来处理区间问题的。

线段树长个啥样子?

展示一个区间  1 - 10 的一颗线段树,就是这么个树东西。

线段树的基本结构 :

1、线段树的每个节点都代表一个区间
2、线段树具有唯一的根节点,代表的区间的整个统计范围,[1,N]
3、线段树的每个叶节点都代表一个长度为 1 的元区间 [x,x],也就是我们原数组中每个值,原数组中有几个值
就有多少个叶子节点(可以参照上图了解一下)。
4、对于每个内部节点 [l,r],它的左子节点是 [l,mid],右子节点是 [mid + 1,r],mid = l + r >> 1(向下取整)

线段树经常处理那些区间问题 ?

1、单点查询(查询某个位置上的值是多少)
2、单点修改(修改某个位置上的值)
3、区间查询(查询某个区间的 和、最大值、最小值、最大公约数、and so on)
4、区间修改(修改某个区间的值, eg:让某个区间都 + 一个数、and so on)

线段树需要注意的地方 :

1、结构体空间一定要开 4 倍,一定要记得看 4 倍(看上面这棵树,按节点编号我们可以看到一共有 25 个节点,但算上空余的位置呢?)
会发现有 31 个节点,可以自己数一下,所以我们要开原数组的 4 倍,避免出现数组越界,非法访问的情况(段错误)。
2、区间判断的时候一定不要写反(下面写的时候就知道了,这个坑让我 Debug 了一个多小时)
3、没事多打打,模板,就当练手速了。

线段树的基本操作 :

1、Struct结构体存储

struct node {
LL l,r;
LL sum; // 看需要向父节点传送什么
} tr[maxn << 2];

2、 Build

void pushup(LL u) {
tr[u].sum = gcd(tr[u << 1].sum,tr[u << 1 | 1].sum);
return ;
} void build(LL u,LL l,LL r) {
tr[u].l = l,tr[u].r = r; // 初始化(节点 u 代表区间 [l,r])
if(l == r) {
tr[u].sum = b[l]; // 递归到叶节点赋初值
return ;
}
LL mid = l + r >> 1; // 折半
build(u << 1,l,mid); // 向左子节点递归
build(u << 1 | 1,mid + 1,r); // 向右子节点递归
pushup(u); // 从下往上传递信息
return;
}

3、Update

void update(LL u,LL x,LL v) {
if(tr[u].l == tr[u].r) { // 找到叶节点
tr[u].sum += v; // 在某个位置加上一个数
return ;
}
LL mid = tr[u].l + tr[u].r >> 1;
if(x <= mid) update(u << 1,x,v); // x 属于左半区间
else update(u << 1 | 1,x,v); // x 属于右半区间
pushup(u); // 从下向上更新信息
return ;
}

4、Query :

1、若 [l,r] 完全覆盖了当前节点代表的区间,则立即回溯。
2、若左子节点与 [l,r] 有重叠部分,则递归访问左子节点。
3、若右子节点与 [l,r] 有重叠部分,则递归访问右子节点。
LL query(int u,int l,int r) {
if(tr[u].l >= l && tr[u].r <= r) { // 完全包含
return tr[u].sum;
}
int mid = tr[u].l + tr[u].r >> 1;
LL sum = 0;
if(l <= mid) sum += query(u << 1,l,r);
if(r > mid) sum += query(u << 1 | 1,l,r);
return sum;
}

上述就是线段树的基本操作,基本上都是围绕单点问题进行操作,如果要涉及到复杂的区间操作,

例如 : 给区间 [l,r] 每个数都 + d

这时如果还用上述操作,我们就需要进行 l - r + 1 次操作,如果有多次这样的操作,显然时间

复杂度会很高,这时候我们应该选择什么样的方法来降低时间复杂度呢 ?

Lazy(懒) 标记应运而生

简单一点来说就是,减少重复的操作,如果说我们操作的每一个数都在一个区间范围内,那么
我们就可以直接处理这个区间,不需要再一个一个处理,比如上面的给区间的每一个数 + d;
假设说我们已经知道 [l,r] 完全包含一个区间 [x,y],也就是说 区间[x,y]是 [l,r]的
一个子区间,那么这个时候我们是不是直接可以计算出 [x,y] 这个区间 都 + d 后的值是
多少, (x - y + 1) * d(假设是求和的话),这样我们就可以不再用去一个一个加,然后
再合并了,我们知道有这样的区间后,怎么用呢?这时候就需要进行标记一下,便于我们知道
这个地方有一个区间可以直接处理,不需要再麻烦着向下继续去处理了,是不是很懒,哈哈。
/*
懒标记的含义 : 该节点曾经被修改,但其子节点尚未被更新。
在后续的指令中,我们需要从某个节点向下递归时,检查该节点是否具有标记,若有标记,就根据
标记信息更新 该节点 的两个子节点,同时为该节点的两个子节点增加标记,然后清楚 p 的标记。
*/
void pushdown(int u) {
if(tr[u].lazy) { // 节点 u 有标记
tr[u << 1].sum += tr[u].lazy * (tr[u << 1].r - tr[u << 1].l + 1); // 更新左子节点信息
tr[u << 1| 1].sum += tr[u].lazy * (tr[u << 1 | 1].r - tr[u << 1 | 1].l + 1); // 更新右子节点
tr[u << 1].lazy += tr[u].lazy; // 给左子节点打延迟标记
tr[u << 1 | 1].lazy += tr[u].lazy; // 给右子节点打延迟标记
tr[u].lazy = 0; // 清楚父节点的延迟标记(这点很重要)
}
return ;
}

加上 Lazy 标记的其他操作 :

// Build 不变
// Update
void modify(int u,int l,int r,int x) {
if(tr[u].l >= l && tr[u].r <= r) { // 完全覆盖
tr[u].sum += (tr[u].r- tr[u].l + 1) * x; // 更新节点信息
tr[u].lazy += x; // 给节点打延迟标记
return ;
}
pushdown(u); // 下传延迟标记
int mid = tr[u].l + tr[u].r >> 1;
if(l <= mid) modify(u << 1,l,r,x);
if(r > mid) modify(u << 1 | 1,l,r,x);
pushup(u);
return ;
} // Query
LL query(int u,int l,int r) {
if(tr[u].l >= l && tr[u].r <= r) {
return tr[u].sum;
}
pushdown(u); // 同上
int mid = tr[u].l + tr[u].r >> 1;
LL sum = 0;
if(l <= mid) sum += query(u << 1,l,r);
if(r > mid) sum += query(u << 1 | 1,l,r);
return sum;
}

总结 :

线段树的操作基本上就这些,哈哈,实际上自己就了解这么多,而且是最近有几场比赛碰见挺多的,就学了一下,
主要是手得多动动,有时候考察得还是比较复杂得,先把这些基础得模板搞懂吧。

例题(模板题):

1、一个简单的整数问题

#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm> using namespace std; const int maxn = 1e5 + 10;
typedef long long LL; struct node {
int l,r;
LL sum,lazy;
}tr[maxn << 2];
int a[maxn];
int n,m;
int l,r; int main(void) {
void build(int u,int l,int r);
void modify(int u,int l,int r,int x);
LL query(int u,int l,int r);
scanf("%d%d",&n,&m);
for(int i = 1; i <= n; i ++) {
scanf("%d",&a[i]);
}
build(1,1,n);
while(m --) {
char ch;
cin >> ch;
if(ch == 'Q') {
scanf("%d",&l);
printf("%lld\n",query(1,1,l) - query(1,1,l - 1));
} else {
int value;
scanf("%d%d%d",&l,&r,&value);
modify(1,l,r,value);
}
}
return 0;
} void pushup(int u) {
tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
return ;
} void pushdown(int u) {
if(tr[u].lazy) {
tr[u << 1].sum += tr[u].lazy * (tr[u << 1].r - tr[u << 1].l + 1);
tr[u << 1| 1].sum += tr[u].lazy * (tr[u << 1 | 1].r - tr[u << 1 | 1].l + 1);
tr[u << 1].lazy += tr[u].lazy;
tr[u << 1 | 1].lazy += tr[u].lazy;
tr[u].lazy = 0;
}
return ;
} void build(int u,int l,int r) {
tr[u].l = l,tr[u].r = r;
if(l == r) {
tr[u].sum = a[l];
return ;
}
int mid = l + r >> 1;
build(u << 1,l,mid);
build(u << 1 | 1,mid + 1,r);
pushup(u);
return ;
} void modify(int u,int l,int r,int x) {
if(tr[u].l >= l && tr[u].r <= r) {
tr[u].sum += (tr[u].r- tr[u].l + 1) * x;
tr[u].lazy += x;
return ;
}
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if(l <= mid) modify(u << 1,l,r,x);
if(r > mid) modify(u << 1 | 1,l,r,x);
pushup(u);
return ;
} LL query(int u,int l,int r) {
if(tr[u].l >= l && tr[u].r <= r) {
return tr[u].sum;
}
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
LL sum = 0;
if(l <= mid) sum += query(u << 1,l,r);
if(r > mid) sum += query(u << 1 | 1,l,r);
return sum;
}

2、一个简单的整数问题2

#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm> using namespace std; const int maxn = 1e5 + 10;
typedef long long LL; struct node {
int l,r;
LL sum,lazy;
}tr[maxn << 2];
int a[maxn];
int n,m;
int l,r; int main(void) {
void build(int u,int l,int r);
void modify(int u,int l,int r,int x);
LL query(int u,int l,int r);
scanf("%d%d",&n,&m);
for(int i = 1; i <= n; i ++) {
scanf("%d",&a[i]);
}
build(1,1,n);
while(m --) {
char ch;
cin >> ch;
if(ch == 'Q') {
scanf("%d%d",&l,&r);
printf("%lld\n",query(1,l,r) );
} else {
int value;
scanf("%d%d%d",&l,&r,&value);
modify(1,l,r,value);
}
}
return 0;
} void pushup(int u) {
tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
return ;
} void pushdown(int u) {
if(tr[u].lazy) {
tr[u << 1].sum += tr[u].lazy * (tr[u << 1].r - tr[u << 1].l + 1);
tr[u << 1| 1].sum += tr[u].lazy * (tr[u << 1 | 1].r - tr[u << 1 | 1].l + 1);
tr[u << 1].lazy += tr[u].lazy;
tr[u << 1 | 1].lazy += tr[u].lazy;
tr[u].lazy = 0;
}
return ;
} void build(int u,int l,int r) {
tr[u].l = l,tr[u].r = r;
if(l == r) {
tr[u].sum = a[l];
return ;
}
int mid = l + r >> 1;
build(u << 1,l,mid);
build(u << 1 | 1,mid + 1,r);
pushup(u);
return ;
} void modify(int u,int l,int r,int x) {
if(tr[u].l >= l && tr[u].r <= r) {
tr[u].sum += (tr[u].r- tr[u].l + 1) * x;
tr[u].lazy += x;
return ;
}
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if(l <= mid) modify(u << 1,l,r,x);
if(r > mid) modify(u << 1 | 1,l,r,x);
pushup(u);
return ;
} LL query(int u,int l,int r) {
if(tr[u].l >= l && tr[u].r <= r) {
return tr[u].sum;
}
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
LL sum = 0;
if(l <= mid) sum += query(u << 1,l,r);
if(r > mid) sum += query(u << 1 | 1,l,r);
return sum;
}

关于线段树的感悟(Segment Tree)的更多相关文章

  1. 线段树基本操作(Segment Tree)

    线段树(Segment Tree) 入门模板题 洛谷oj P3372 题目描述 如题,已知一个数列,你需要进行下面两种操作: 1.将某区间每一个数加上x 2.求出某区间每一个数的和 输入格式 第一行包 ...

  2. 【UOJ#388】【UNR#3】配对树(线段树,dsu on tree)

    [UOJ#388][UNR#3]配对树(线段树,dsu on tree) 题面 UOJ 题解 考虑一个固定区间怎么计算答案,把这些点搞下来建树,然后\(dp\),不难发现一个点如果子树内能够匹配的话就 ...

  3. bzoj3307雨天的尾巴(权值线段树合并/DSU on tree)

    题目大意: 一颗树,想要在树链上添加同一物品,问最后每个点上哪个物品最多. 解题思路: 1.线段树合并 假如说物品数量少到可以暴力添加,且树点极少,我们怎么做. 首先在一个树节点上标记出哪些物品有多少 ...

  4. CF600E Lomsat gelral——线段树合并/dsu on tree

    题目描述 一棵树有$n$个结点,每个结点都是一种颜色,每个颜色有一个编号,求树中每个子树的最多的颜色编号的和. 这个题意是真的窒息...具体意思是说,每个节点有一个颜色,你要找的是每个子树中颜色的众数 ...

  5. 树链剖分+线段树 CF 593D Happy Tree Party(快乐树聚会)

    题目链接 题意: 有n个点的一棵树,两种操作: 1. a到b的路径上,给一个y,对于路径上每一条边,进行操作,问最后的y: 2. 修改某个条边p的值为c 思路: 链上操作的问题,想树链剖分和LCT,对 ...

  6. [LintCode] Segment Tree Build II 建立线段树之二

    The structure of Segment Tree is a binary tree which each node has two attributes startand end denot ...

  7. [LintCode] Segment Tree Build 建立线段树

    The structure of Segment Tree is a binary tree which each node has two attributes start and end deno ...

  8. Aizu 2450 Do use segment tree 树链剖分+线段树

    Do use segment tree Time Limit: 1 Sec Memory Limit: 256 MB 题目连接 http://www.bnuoj.com/v3/problem_show ...

  9. 『线段树 Segment Tree』

    更新了基础部分 更新了\(lazytag\)标记的讲解 线段树 Segment Tree 今天来讲一下经典的线段树. 线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间 ...

随机推荐

  1. 【linux学习笔记】

    网上看一个两小时突击linux的教程,就想补充一下linux的知识.想着一天抽出俩小时立马就能学完呢,结果乱七八糟的事情拖了四五天,实际完成某项任务的时间超出预期完成任务的两部不止.好了," ...

  2. 跟着知识追寻者学BeautifulSoup,你学不会打不还口,骂不还手

    一 前言 Beautiful Soup 是一个可以从HTML或XML文件中提取数据的Python库:其强大的提取能力让知识追寻者放弃了使用正则匹配查找HTML节点:Beautifu Soup 其能直接 ...

  3. Kaggle竞赛丨入门手写数字识别之KNN、CNN、降维

    引言 这段时间来,看了西瓜书.蓝皮书,各种机器学习算法都有所了解,但在实践方面却缺乏相应的锻炼.于是我决定通过Kaggle这个平台来提升一下自己的应用能力,培养自己的数据分析能力. 我个人的计划是先从 ...

  4. git 工作中实用 多人协同开发

    多人协同开发 .克隆分支 git clone -b dev1. url .创建并关联远程分支 git checkout -b dev_wt origin/dev_wt 情况一获取其它分支的代码,并合并 ...

  5. 团队项目——Alpha发布2

    一.作业描述 这个作业属于哪个课程 这个作业要求在哪里 团队名称 CTRL-IKun 这个作业的目标 在这个星期内完成团队项目α版本的第二次测试和发布,完善出错设置 二.成员列表 姓名 学号列表 廖志 ...

  6. 理解Javascript的柯里化

    前言 本文1454字,阅读大约需要4分钟. 总括: 本文以初学者的角度来阐述Javascript中柯里化的概念以及如何在工作中进行使用. 原文地址:理解Javascript的柯里化 知乎专栏: 前端进 ...

  7. 3d动态文字的绘制

    在这里介绍一种3D文字的一种动态效果,可以说这是一种伪3D创建的一种3D的视觉效果 简单的讲解一下:大家或多或少都会听说过素描这种绘画手法,其实这种手法就是巧妙的利用了.阴影给人们带来的立体的视觉冲击 ...

  8. Spring 中读取文件-ResourceLoaderAware

    Spring 中读取文件-ResourceLoaderAware 概述 Spring ResourceLoader为我们提供了一个统一的getResource()方法来通过资源路径检索外部资源.从而将 ...

  9. 吉哥系列故事——恨7不成妻(数位dp)

    吉哥系列故事--恨7不成妻 传送门 Problem Description 单身! 依然单身! 吉哥依然单身! DS级码农吉哥依然单身! 所以,他生平最恨情人节,不管是214还是77,他都讨厌! 吉哥 ...

  10. SpringBoot检索篇Ⅳ --- 整合ElasticSearch

    知识储备:  关于ElasticSearch的基本使用我已经在上一篇文章介绍过了(传送门),本篇文章主要讲述的是SpringBoot与ElasticSearch的整合使用. SpringBoot与El ...