首页 > 其他分享 >学习笔记——线段树

学习笔记——线段树

时间:2024-01-17 21:33:59浏览次数:34  
标签:int 线段 tree 笔记 节点 学习 add mul sum

线段树(Segment Tree)

1.建树

首先我们要明白线段树中的每个节点都代表一个区间,而对于线段树中的每个内部节点 \(\left[l,r\right]\),它的左子节点是 \(\left[l,mid\right]\),右子节点是 \(\left[mid + 1,r\right]\),其中 \(mid = (l+r)/2\)(向下取整)。
然后我们可以让根节点的编号为 \(1\),而编号为 \(x\) 的节点的左子节点编号为 \(x*2\),右子节点编号为 \(x*2+1\)。这样的话,就能用结构体来存储线段树了,但要注意存储线段树的数组长度要不小于 \(4N\),其中 \(N\) 为节点个数。
线段树基本就是对序列进行维护,以区间求和为例,代码如下:

struct Tree{
    int l,r,sum;
}tree[SIZE*4];
void build(int k,int l,int r){
    tree[k].l=l,tree[k].r=r;
	if(l==r){
		tree[k].sum=a[l];
		return;
	}
	int mid=(l+r)/1;
	build(k*2,l,mid);
	build(k*2+1,mid+1,r);
	tree[k].sum=tree[k*2].sum+tree[k*2+1].sum;//从下往上传递信息
}

build(1,1,n);//调用

2. 单点修改

单点修改是将 \(A_x\) 的值修改为 \(v\)。
我们需要从根节点开始,递归寻找到代表区间 \(\left[x,x\right]\) 的叶子节点,然后从下往上更新它以及它的所有祖先节点上保存的信息,时间复杂度为 \(\mathcal{O}(log N)\)。

void change(int k,int x,int v){
    if(tree[k].l==tree[k].r){
        tree[k].sum=v;
        return;
    }
    int mid=(tree[k].l+tree[k].r)/2;
    if(x<=mid)change(k*2,x,v);
    else change(k*2+1,x,v);
    tree[k].sum=tree[k*2].sum+tree[k*2+1].sum;
}

change(1,x,v);//调用

3. 区间查询

这里以区间求和为例,查询一个序列 \(A\) 的区间 \(\left[l,r\right]\) 的和,即 \(\sum\limits_{i=l}^r A_i\)。
我们只需要从树根开始递归即可。

int query(int k,int l,int r){
	if(tree[k].l>=l&&tree[k].r<=r){
		return tree[k].sum;
	}
	int ans=0,mid=(tree[k].l+tree[k].r)/2;
	if(mid>=l)ans+=query(k*2,l,r);
	if(mid<r)ans+=query(k*2+1,l,r);
	return ans;
}

4. 延迟标记

我们在执行修改指令时,在回溯之前向此节点 \(p\) 增加一个标记,标识此节点被修改但子节点未更新。在后续指令中,如果要从 \(p\) 向下递归,就再检查它是否具有标记,如果有,则根据标记更新子节点,并为子节点增加标记,删除 \(p\) 的标记。
总的来说,就是对任意节点的修改操作都延迟到“后续操作中递归进入其父节点时”再执行,这里以 Luogu P2023 为例。

#include<bits/stdc++.h>
#define int long long
#define MAX 100005
using namespace std;
int n,p,m,a[MAX],op,t,g,c;
struct MRS{
    int l,r,sum,add,mul;
}tree[MAX*4];
void build(int k,int l,int r){
    tree[k].l=l,tree[k].r=r,tree[k].mul=1;
    if(l==r){
        tree[k].sum=a[l]%p;
        return;
    }
    int mid=(l+r)>>1;
    build(k*2,l,mid);
    build(k*2+1,mid+1,r);
    tree[k].sum=(tree[k*2].sum+tree[k*2+1].sum)%p;
}
void pushdown(int k){
    // if(tree[k].add){
        tree[k*2].sum=(tree[k].mul*tree[k*2].sum+(tree[k].add*(tree[k*2].r-tree[k*2].l+1))%p)%p;
        tree[k*2+1].sum=(tree[k].mul*tree[k*2+1].sum+(tree[k].add*(tree[k*2+1].r-tree[k*2+1].l+1))%p)%p;
        tree[k*2].mul=(tree[k].mul*tree[k*2].mul)%p;
        tree[k*2+1].mul=(tree[k].mul*tree[k*2+1].mul)%p;
        tree[k*2].add=(tree[k].mul*tree[k*2].add+tree[k].add)%p;
        tree[k*2+1].add=(tree[k].mul*tree[k*2+1].add+tree[k].add)%p;
        tree[k].add=0;
        tree[k].mul=1;
    // }
}
void ADD(int k,int l,int r,int d){
    if(tree[k].l>=l&&tree[k].r<=r){
        tree[k].sum+=d*(tree[k].r-tree[k].l+1);
        tree[k].sum%=p;
        tree[k].add=(tree[k].add+d)%p;
        return;
    }
    pushdown(k);
    tree[k].sum=(tree[k*2].sum+tree[k*2+1].sum)%p;
    int mid=(tree[k].l+tree[k].r)>>1;
    if(l<=mid)ADD(k*2,l,r,d);
    if(r>mid)ADD(k*2+1,l,r,d);
    tree[k].sum=(tree[k*2].sum+tree[k*2+1].sum)%p;
}
void MUL(int k,int l,int r,int d){
    if(tree[k].l>=l&&tree[k].r<=r){
        tree[k].sum=(tree[k].sum*d)%p;
        tree[k].add=(tree[k].add*d)%p;
        tree[k].mul=(tree[k].mul*d)%p;
        return;
    }
    pushdown(k);
    tree[k].sum=(tree[k*2].sum+tree[k*2+1].sum)%p;
    int mid=(tree[k].l+tree[k].r)>>1;
    if(l<=mid)MUL(k*2,l,r,d);
    if(r>mid)MUL(k*2+1,l,r,d);
    tree[k].sum=(tree[k*2].sum+tree[k*2+1].sum)%p;
}
int query(int k,int l,int r){
    if(tree[k].l>=l&&tree[k].r<=r){
        return tree[k].sum;
    }
    pushdown(k);
    int mid=(tree[k].l+tree[k].r)>>1,ans=0;
    if(l<=mid)ans+=query(k*2,l,r),ans%=p;
    if(r>mid)ans+=query(k*2+1,l,r),ans%=p;
    return ans;
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin>>n>>p;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    cin>>m;
    build(1,1,n);
    for(int i=1;i<=m;i++){
        cin>>op;
        if(op==1){
            cin>>t>>g>>c;
            MUL(1,t,g,c);
        }else if(op==2){
            cin>>t>>g>>c;
            ADD(1,t,g,c);
        }else if(op==3){
            cin>>t>>g;
            cout<<query(1,t,g)<<'\n';
        }
    }
    return 0;
}

总而言之,线段树在 \(\mathcal O(log n)\) 的时间内解决区间修改与查询问题中及其有用,值得学习。

标签:int,线段,tree,笔记,节点,学习,add,mul,sum
From: https://www.cnblogs.com/MithrilSwordXIV/p/17968669

相关文章

  • Stack-array based implementation【1月17日学习笔记】
    点击查看代码//Stack-arraybasedimplementation#include<iostream>usingnamespacestd;#defineMAX_SIZE101intA[MAX_SIZE];//globleinttop=-1;//globlevoidpush(intx){ if(top==MAX_SIZE-1){ cout<<"error:stackoverflow"&l......
  • Numpy学习笔记
    1、创建数组直接创建数组np.array([1,2,3,4])创建指定形状和内容的数组numpy.zeros(shape,dtype=float,order='C')numpy.ones(shape,dtype=float,order='C')numpy.empty(shape,dtype=float,order='C')参数描述shape数组形状dtype数据类......
  • Doubly linked list【1月17日学习笔记】
    点击查看代码//Doublylinkedlist#include<iostream>usingnamespacestd;structnode{ intdata; node*next; node*prev;};//定义双向链表结构体node*A;node*getnewnode(intx){ node*temp=newnode; temp->data=x; temp->prev=NULL; temp->nex......
  • 【学习笔记】后缀自动机 SAM
    一.后缀自动机的定义SAM(SuffixAutomaton)是一种有限状态自动机,仅可以接受一个字符串的所有后缀。如果您不懂自动机,那么换句话说:SAM是一个有向无环图。称每个结点为状态,边为状态间的转移,每个转移标有一个字母,同一节点引出的转移不同。SAM存在一个源点\(S\),称为初始状态......
  • 【学习笔记】整体二分
    一.整体二分概念整体二分的主体思路就是把多个查询一起解决,是一个离线算法。其要求:询问的答案具有可二分性修改对判定答案的贡献互相独立,修改之间互不影响效果修改如果对判定答案有贡献,则贡献为一确定的与判定标准无关的值贡献满足交换律,结合律,具有可加性题目允......
  • 【学习笔记】线段树合并
    一.普通线段树合并线段树合并就是建立一棵新的线段树保存原有的两棵线段树的信息。两棵线段树当前要合并的点所表示的区间是一样的。线段树合并的过程很简单。如果A有p位置,B没有,新的线段树p位置赋成A,返回A;如果B有p位置,A没有,新的线段树p位置赋成B,返回A;如果合并到叶子结......
  • 【学习笔记】权值线段树
    一.权值线段树权值线段树即一种线段树,以序列的数值为下标。节点里所统计的值为节点所对应的区间\([l,r]\)中,\([l,r]\)这个值域中所有数的出现次数。举个例子,有一个长度为\(10\)的序列\(\{1,5,2,3,4,1,3,4,4,4\}\)。那么统计每个数出现的次数。易知\(1\)出现了\(2\)......
  • 【学习笔记】数论杂谈
    一.素数相关0.约定若无特殊说明,本部分做以下记号规定。\(p\in\mathbb{P}\),\(\mathbbP\)为质数集。\(\pi(n)\)表示\(1\)至\(n\)内的素数个数。\(P^{+}(n),P^-(n)\)分别表示\(n\)的最大/最小质因子。\(\nu_i\)表示\(i\)的可重质因子个数。1.素数定理\[\pi(......
  • 【学习笔记】数论函数与莫比乌斯反演
    一.数论函数基础数论函数:满足值域为整数的函数。本文下述的数若无特殊说明均为整数。若无特殊说明则钦定\(\displaystylen=\prod_{i=1}^kp_i^{e_i},p_i\in\mathbb{P}\)。\(\mathbb{P}\)表示质数集合,\(p_i\)互不相同。介绍几个常见的数论函数:\(I(n)\):恒等函数,无论\(n\)......
  • 【学习笔记】Segment Tree Beats/吉司机线段树
    一.区间最值操作本文对吉如一老师在\(2016\)年国家集训队论文中的线段树处理历史区间最值的问题的一些杂谈。区间最值笼统地指求区间的最值以及区间所有数对\(x\)取最值(即令\(a_i=\max/\min(a_i,x)\))这一类的查询与修改操作。HDU5306GorgeousSequence支持对区间......