首页 > 其他分享 >数据结构【线段树】

数据结构【线段树】

时间:2024-02-28 09:46:10浏览次数:27  
标签:前缀 int 线段 tr sum 数据结构 节点

对于一个数据结构而言,我们总要能对其进行两件事:修改和操作。操作在这里是一个专有名词,专门指代求最值、求和等操作,具体能指代什么操作之后再聊。

 

如果朴素的用数组进行存储,那么修改是O(1)的,而操作往往是O(n)的。

当操作指的是求和的时候,我们可以使用前缀和算法,前缀和使得操作是O(1)的,然而,前缀和同时也使得修改是O(n)的。

对于其他操作,也有类似于前缀和一样的较为简单的做法使得操作变成一个O(1)的过程,但他们却也同样会这的修改变成了一个O(n)的过程,我们讲包括前缀和本身在内的这些朴素优化方法统称为类前缀和做法。

 

如果我们要同时进行大量修改和操作,那么不管是朴素数组法还是类前缀和做法,总的时间复杂度都是O(n^2)的,那么有没有什么办法可以简化这一点呢?有的,线段树。线段树可以使得操作和修改都变成O(logn)的过程,虽然O(logn)比不过O(1),但是看总时间复杂度时,O(nlogn)相比于O(n^2)可谓是遥遥领先,这便是这一数据结构的优点。

 

至于树状数组,则是线段树的特化版,节约了75%的空间与90%的时间,牺牲了适用范围的结果,详见我的《树状数组》一文

 

这里继续介绍线段树

 

线段树的核心思想在于把原序列用递归式的二分法化成多个区间,从而形成一个二叉树,树的每个非叶子节点都对应着一个区间,叶节点则对应着原序列的特定元素。每一个节点代表着的是一个区间,存储的内容既包括区间的左右端点,还包括与这区间有关相关数据。比如如果操作指的是求和,那么存储的内容便是区间元素的和。

 

建树的过程相当于是在预处理信息,是一个O(n)的过程,单点修改和区间查询都是一个递归的O(logN)过程,,区间修改则由于懒惰标记的引入,也是一个O(logN)的过程,代码如下

 

 

const int MaxN=100000+5;
int a[MaxN],Nmax=-1;
struct node
{
    int l,r,sum,add;
    node(){l=r=sum=add=0;}
    node(int L,int R,int SUM,int ADD){l=L;r=R;sum=SUM;add=ADD;}
}tr[MaxN*4];
//x是节点编号,根据完全二叉树的性质可知,x节点的左右子节点编号为x*2,x*2+1
void _build(int x,int l,int r)
{
    tr[x].l=l,tr[x].r=r;//节点表示区间的左右界    
    if(l==r)
    {
        //若l=r,说明这个节点是叶子节点,直接赋值        
        tr[x].sum=a[l];//a是原数列   
        Nmax=max(Nmax,x);     
        return;
    }
    int mid=(l+r)/2;//mid表示左右子区间的间隔            
    _build(x<<1,l,mid),_build((x<<1)+1,mid+1,r);//递归建树   
    tr[x].sum=tr[x<<1].sum+tr[(x<<1)+1].sum;//pushup操作
}
inline void build(int r){_build(1,1,r);}
//区间查询
inline void pushdown(int now)
{
    if(tr[now].add==0)return;
    tr[now<<1].add+=tr[now].add,tr[(now<<1)+1].add+=tr[now].add;
    tr[now<<1].sum+=tr[now].add*(tr[now<<1].r-tr[now<<1].l+1);
    tr[(now<<1)+1].sum+=tr[now].add*(tr[(now<<1)+1].r-tr[(now<<1)+1].l+1);
    tr[now].add=0;
}
int _query(int x,int l,int r)
{
    if(tr[x].l>=l&&tr[x].r<=r) return tr[x].sum;//如果该节点的区间被要查找的区间包括了,那么就不用继续找了,直接返回改节点的值就行了
    pushdown(x);
    int mid=(tr[x].l+tr[x].r)>>1;
    int sum=0;
    if(l<=mid&&r>=tr[x].l) sum+=_query(x<<1,l,r);//如果当前节点在要查找区间左边界的右面,那么递归查找左子树
    if(r>=mid+1&&l<=tr[x].r) sum+=_query((x<<1)+1,l,r);//如果当前节点在要查找区间右边界的左面,那么递归查找右子树
    return sum;//由此得出了该区间的值,返回即可
}
inline int query(int l,int r){return _query(1,l,r);}
//单点修改
void _change(int now,int x,int k)
{
    if(tr[now].l==tr[now].r){tr[now].sum=k;return;}
    pushdown(x);
    int mid=(tr[now].l+tr[now].r)>>1;
    if(x<=mid)_change((now<<1),x,k);
    else _change((now<<1)+1,x,k);
    tr[now].sum=tr[now<<1].sum+tr[(now<<1)+1].sum;
}
inline void change(int x,int k){_change(1,x,k);}
//某节点被打了懒惰标记表示它本身修改了,但他的子节点尚未修改
void _update(int now,int l,int r,int k)
{
    if(r<tr[now].l||l>tr[now].r)return;
    if(l<=tr[now].l&&tr[now].r<=r)
    {
        //cout<<"update"<<tr[now].l<<tr[now].r<<endl;
        tr[now].sum+=k*(tr[now].r-tr[now].l+1);//先修改这个区间
        tr[now].add+=k;//然后给它打上懒标记
        return;
    }
    pushdown(now);
    int mid=(tr[now].l+tr[now].r)/2;
    if(l<=mid&&r>=tr[now].l)_update(now<<1,l,r,k);
    if(r>=mid+1&&l<=tr[now].r)_update((now<<1)+1,l,r,k);
    tr[now].sum=tr[now<<1].sum+tr[(now<<1)+1].sum;
    //如果是叶节点,标记会被下传到不会被访问到的虚空节点去,便相当于是消除标记了,因而无需单独特判叶节点
}
void update(int l,int r,int k){_update(1,l,r,k);}
View Code

 

TRANSLATE with x English
Arabic Hebrew Polish
Bulgarian Hindi Portuguese
Catalan Hmong Daw Romanian
Chinese Simplified Hungarian Russian
Chinese Traditional Indonesian Slovak
Czech Italian Slovenian
Danish Japanese Spanish
Dutch Klingon Swedish
English Korean Thai
Estonian Latvian Turkish
Finnish Lithuanian Ukrainian
French Malay Urdu
German Maltese Vietnamese
Greek Norwegian Welsh
Haitian Creole Persian  
  TRANSLATE with COPY THE URL BELOW Back EMBED THE SNIPPET BELOW IN YOUR SITE Enable collaborative features and customize widget: Bing Webmaster Portal Back     此页面的语言为中文(简体)   翻译为        
  • 中文(简体)
  • 中文(繁体)
  • 丹麦语
  • 乌克兰语
  • 乌尔都语
  • 亚美尼亚语
  • 俄语
  • 保加利亚语
  • 克罗地亚语
  • 冰岛语
  • 加泰罗尼亚语
  • 匈牙利语
  • 卡纳达语
  • 印地语
  • 印尼语
  • 古吉拉特语
  • 哈萨克语
  • 土耳其语
  • 威尔士语
  • 孟加拉语
  • 尼泊尔语
  • 布尔语(南非荷兰语)
  • 希伯来语
  • 希腊语
  • 库尔德语
  • 德语
  • 意大利语
  • 拉脱维亚语
  • 挪威语
  • 捷克语
  • 斯洛伐克语
  • 斯洛文尼亚语
  • 旁遮普语
  • 日语
  • 普什图语
  • 毛利语
  • 法语
  • 波兰语
  • 波斯语
  • 泰卢固语
  • 泰米尔语
  • 泰语
  • 海地克里奥尔语
  • 爱沙尼亚语
  • 瑞典语
  • 立陶宛语
  • 缅甸语
  • 罗马尼亚语
  • 老挝语
  • 芬兰语
  • 英语
  • 荷兰语
  • 萨摩亚语
  • 葡萄牙语
  • 西班牙语
  • 越南语
  • 阿塞拜疆语
  • 阿姆哈拉语
  • 阿尔巴尼亚语
  • 阿拉伯语
  • 韩语
  • 马尔加什语
  • 马拉地语
  • 马拉雅拉姆语
  • 马来语
  • 马耳他语
  • 高棉语
 

标签:前缀,int,线段,tr,sum,数据结构,节点
From: https://www.cnblogs.com/gongkai/p/18039025

相关文章

  • CCPC2023深圳 K-四国军棋(线段树维护单调栈哈希值)
    传送门解题思路对于每个人的棋子,总是最高的那个棋子发挥决定性作用,被消耗后,再看剩下的最高的棋子。这就相当于单调不递增栈的维护过程。最后就要比较两个人的单调不递增栈是否完全相同。和经典的楼房重建相似,但是这个题不止需要维护单调栈的长度,还要维护哈希值。我是分开写的......
  • CF932F Escape Through Leaf【DP,李超线段树】
    有一颗\(n\)个节点的树,根节点为\(1\)。每个节点有两个权值,第\(i\)个节点的权值为\(a_i,b_i\)。你可以从一个节点跳到它的子树内任意一个节点上。从节点\(x\)跳到节点\(y\)一次的花费为\(a_x\timesb_y\)。跳跃多次走过一条路径的总费用为每次跳跃的费用之和。求每个节......
  • Qt 常见数据结构详解:从基本框架到实际应用
    在Qt框架中,数据结构的选择对于提高代码效率和性能至关重要。正确地使用数据结构可以显著提高应用程序的效率和响应速度。下面我们将详细介绍Qt中常见的几种数据结构,包括QString、QList、QVector、QMap、QHash、QSet和QPair。1.QStringQString是Qt中用于处理字符串的类。......
  • 线段树合并
    线段树合并1权值线段树1.1权值线段树的基本思想权值线段树其实比较简单。正常的线段树是维护区间上每一个点的值,而权值线段树则是维护每一个数字出现的次数(可以类比为桶)。例如原本的$1-4$表示区间$[1,4]$上数字的和(或差、最大值等等),现在就表示数字$1-4$的出现次数之......
  • 算法入门:数据结构
    文章目录1.什么是算法和数据结构2.算法2.1.算法的特性2.2.算法设计的要求3.数据结构3.1.数组3.1.1.数组的定义3.1.2.数组的基本特性3.1.3.多维数组3.1.4.数组的同质性3.1.5.动态数组3.1.6.数组的优缺点3.1.7.数组的应用场景3.1.8.结论3.2.链表3.2.1.链表的定义......
  • 类:数据结构(模板)、数据类型(反射)、种类(amount)
    1.析构函数:在GC回收资源时,我们可以在析构函数中做事情; 2.也可以不用new关键字进行创建对象: 使用dynamic,可以直接调用name 3.静态构造器只能初始化静态成员 ......
  • 古人云:时间线段树 爽!时间线段树学习笔记。
    嘛,这个东西虽然叫时间线段树,但是和线段树好像关系并不大,只是借用了一下线段树的结构。算法介绍这个算法是用来解决这类问题的:每个操作只在一段时间内生效,然后询问某个时间点所有操作的贡献。于是我们考虑离线,对整个时间序列建一个线段树,每次操作相当于是在这个线段树上进行了区......
  • 线性数据结构:数组、受限数组(栈、队列)、线性表
    1.数组数组定义  数组(Array)是有序的元素序列。属于线性结构(有且仅有一个前驱、有且仅有一个后继)。数组特点  数组的关键在于在内存中的物理地址对应的是一段连续的内存。这意味着如果想要在任意位置删除/新增一个元素,那么该位置往后的所有元素,都需要往前挪/往后挪一个位......
  • 可持久化线段树 2
    可持久化线段树前言这个东西之前讲过,但是用得少,很快就忘了。我又看了我之前的那篇笔记,简直就是胡言乱语。所了解的太浅了。最近在刷数据结构,于是决定再写一篇。但是,之前那篇不打算删了,想看黑历史的可以去看。算法概要可持久——即可以保存历史版本。我们如何得到一棵可以......
  • 计算机基础知识问答-数据结构篇
    阐述栈与队列的相同点和不同点相同点:栈和队列都是线性数据结构,用于存储数据的集合。在栈和队列中,数据的插入和删除操作都遵循特定的规则。不同点:插入与删除操作的位置:栈是后进先出(LIFO,LastInFirstOut)的数据结构,只允许在栈顶进行插入(push)和删除(pop)操作。队列是先进......