数据结构:线段树实现详解

本质为 加了左右端点的树形结构

线段树

数据类型构造

struct node{
	int l,r;  //l左端点    r右端点
	int val;  //val值域
}n[maxn];     //n[i]表示标号为i的树的结点

基本操作

构造线段树

void make(int left,int right, int num);

参数说明:

  left—right:即左端点—右端点,最大区间;

  num:标号,一般为1。

添加结点

void add(int i,int j,int num);

参数说明:

  i:欲加到的目的点;

  j:欲加点的个数;

  num:标号,一般为1。

删除结点

void sub(int i,int j,int num);

参数说明:

  i:欲加到的目的点;

  j:欲加点的个数;

  num:标号,一般为1。

查询结点

void query(int l, int r,int num);

参数说明:

  l-r:询问区间,该函数会利用sum存储的权值和来查询l-r的个数;

  num:标号,一般为1.

实现原理

构造线段树

利用性质:

  1> 标号为i的结点,其左孩子标号2i,右孩子标号2i+1,父亲标号i/2;

  2> 线段树中,两端点为a、b的一个点,其左孩子两端点为a、mid,右孩子两端点mid+1,b;

  3> 左右端点相等时,访问到叶子结点

思路:

对于每个结点

确定左右端点lr
递归询左孩子
递归询问右孩子
计算val值

val计算方法为:val = 左.val + 右.val

添加结点

  自顶向下的树的操作,自根加到结点的操作。

思路:

  从根向下访问,若i在左孩子区间内,则+j,否则访问右孩子。(i不在左孩子中,则必在右孩子中

从根开始,到叶子结点停止

Created with Raphaël 2.2.0 val+j 左孩子是否包含i? 递归访问左孩子 递归访问右孩子 yes no

删除结点

添加结点的反操作,将 ’+‘ 改为 ’-‘ 即可。

查询结点

设当前点的两端为a、b,目标点的两端为l、r

利用分类思想

situation1

situation2

situation3

situation4

l、r在哪个区段内,就访问哪个区段

思路:

Created with Raphaël 2.2.0 l-r是否包含a-b? 记录权值和 l,r在左孩子中? 递归访问左孩子 l,r在右孩子中? 递归访问右孩子 l,r在左、右孩子中? 递归访问左孩子和右孩子 yes no yes no yes no yes

C++代码实现

构建线段树

void make(int x, int y, int num){//构造
	t[num].l=x,t[num].r=y;
	if(x==y)
		t[num].val=val;  //到叶子结点赋值,结束
	else{
		make(x, ( x + y ) / 2, 2 * num);//递归构造左子树
		make(( x + y ) / 2 + 1, y, 2 * num + 1);//递归构造右子树
		t[num].val = t[2*num].val + t[2*num+1].val;//计算val
	}	
}

添加结点

void add(int i, int j, int num){//增加
	t[num].val += j;//val+j
	if(t[num].l==i && t[num].r==i)//找到结点
		return;
	if(i>(t[num].l + t[num].r) / 2)//i在右孩子中
		add(i, j, 2 * num + 1);
	else						//i在左孩子中
		add(i, j, 2 * num);
}

删除结点

void add(int i, int j, int num){//增加
	t[num].val -= j;//val+j
	if(t[num].l==i && t[num].r==i)//找到结点
		return;
	if(i>(t[num].l + t[num].r) / 2)//i在右孩子中
		add(i, j, 2 * num + 1);
	else						//i在左孩子中
		add(i, j, 2 * num);
}

查询结点

void query(int l, int r, int num){//查询
	if(l<=t[num].l && r>=t[num].r)//在目标区间内,记录权值和SUM
		SUM += t[num].val;
	else{			//不在目标区间内,进行分类
		int mid = (t[num].l + t[num].r)/2;
		if(l>mid)//在右孩子中
			query(l, r, 2 * num + 1);
		else if(r<=mid)//在左孩子中
			query(l, r, 2 * num);
		else{//左右孩子中均有
			query(l, r, 2 * num);
			query(l, r, 2 * num + 1);
		}
	}
}

Lazy tags优化

区间更新:修改一段连续区间的值

Lazy tags优化:

  如名所示,懒标记优化法(众所周知,懒人创造效率

  关键思路:修改时只修改对查询有用的点,对于未更新完全的点打上懒标记,待需要时再进行更新

实例应用

  1. P3372 【模板】线段树 1
    题目描述:
         如题,已知一个数列,你需要进行下面两种操作:
        1. 将某区间每一个数加上 k。
        2. 求出某区间每一个数的和。
    代码如下:
#include<iostream>
#include<cstdio>
#define MAXN 1000001
#define ll long long
using namespace std;
unsigned ll n,m,a[MAXN],ans[MAXN<<2],tag[MAXN<<2];
inline ll ls(ll x)
{
    return x<<1;
}
inline ll rs(ll x)
{
    return x<<1|1;
}
void scan()
{
    cin>>n>>m;
    for(ll i=1;i<=n;i++)
    scanf("%lld",&a[i]);
}
inline void push_up(ll p)
{
    ans[p]=ans[ls(p)]+ans[rs(p)];
}
void build(ll p,ll l,ll r)
{
    tag[p]=0;
    if(l==r){ans[p]=a[l];return ;}
    ll mid=(l+r)>>1;
    build(ls(p),l,mid);
    build(rs(p),mid+1,r);
    push_up(p);
} 
inline void f(ll p,ll l,ll r,ll k)
{
    tag[p]=tag[p]+k;
    ans[p]=ans[p]+k*(r-l+1);
}
inline void push_down(ll p,ll l,ll r)
{
    ll mid=(l+r)>>1;
    f(ls(p),l,mid,tag[p]);
    f(rs(p),mid+1,r,tag[p]);
    tag[p]=0;
}
inline void update(ll nl,ll nr,ll l,ll r,ll p,ll k)
{
    if(nl<=l&&r<=nr)
    {
        ans[p]+=k*(r-l+1);
        tag[p]+=k;
        return ;
    }
    push_down(p,l,r);
    ll mid=(l+r)>>1;
    if(nl<=mid)update(nl,nr,l,mid,ls(p),k);
    if(nr>mid) update(nl,nr,mid+1,r,rs(p),k);
    push_up(p);
}
ll query(ll q_x,ll q_y,ll l,ll r,ll p)
{
    ll res=0;
    if(q_x<=l&&r<=q_y)return ans[p];
    ll mid=(l+r)>>1;
    push_down(p,l,r);
    if(q_x<=mid)res+=query(q_x,q_y,l,mid,ls(p));
    if(q_y>mid) res+=query(q_x,q_y,mid+1,r,rs(p));
    return res;
}
int main()
{
    ll a1,b,c,d,e,f;
    scan();
    build(1,1,n);
    while(m--)
    {
        scanf("%lld",&a1);
        switch(a1)
        {
            case 1:{
                scanf("%lld%lld%lld",&b,&c,&d);
                update(b,c,1,n,1,d);
                break;
            }
            case 2:{
                scanf("%lld%lld",&e,&f);
                printf("%lld\n",query(e,f,1,n,1));
                break;
            }
        }
    }
    return 0;
}
  1. P3373 【模板】线段树 2
    题目描述:
         如题,已知一个数列,你需要进行下面三种操作:
        1. 将某区间每一个数乘上 x
        2. 将某区间每一个数加上 x
        3. 求出某区间每一个数的和
    代码如下:
#include <iostream>
#include <cstdio>
using namespace std;
//题目中给的p
int p;
//暂存数列的数组
long long a[100007];
//线段树结构体,v表示此时的答案,mul表示乘法意义上的lazytag,add是加法意义上的
struct node{
    long long v, mul, add;
}st[400007];
//buildtree
void bt(int root, int l, int r){
//初始化lazytag
    st[root].mul=1;
    st[root].add=0;
    if(l==r){
        st[root].v=a[l];
    }
    else{
        int m=(l+r)/2;
        bt(root*2, l, m);
        bt(root*2+1, m+1, r);
        st[root].v=st[root*2].v+st[root*2+1].v;
    }
    st[root].v%=p;
    return ;
}
//核心代码,维护lazytag
void pushdown(int root, int l, int r){
    int m=(l+r)/2;
//根据我们规定的优先度,儿子的值=此刻儿子的值*爸爸的乘法lazytag+儿子的区间长度*爸爸的加法lazytag
    st[root*2].v=(st[root*2].v*st[root].mul+st[root].add*(m-l+1))%p;
    st[root*2+1].v=(st[root*2+1].v*st[root].mul+st[root].add*(r-m))%p;
//很好维护的lazytag
    st[root*2].mul=(st[root*2].mul*st[root].mul)%p;
    st[root*2+1].mul=(st[root*2+1].mul*st[root].mul)%p;
    st[root*2].add=(st[root*2].add*st[root].mul+st[root].add)%p;
    st[root*2+1].add=(st[root*2+1].add*st[root].mul+st[root].add)%p;
//把父节点的值初始化
    st[root].mul=1;
    st[root].add=0;
    return ;
}
//update1,乘法,stdl此刻区间的左边,stdr此刻区间的右边,l给出的左边,r给出的右边
void ud1(int root, int stdl, int stdr, int l, int r, long long k){
//假如本区间和给出的区间没有交集
    if(r<stdl || stdr<l){
        return ;
    }
//假如给出的区间包含本区间
    if(l<=stdl && stdr<=r){
        st[root].v=(st[root].v*k)%p;
        st[root].mul=(st[root].mul*k)%p;
        st[root].add=(st[root].add*k)%p;
        return ;
    }
//假如给出的区间和本区间有交集,但是也有不交叉的部分
//先传递lazytag
    pushdown(root, stdl, stdr);
    int m=(stdl+stdr)/2;
    ud1(root*2, stdl, m, l, r, k);
    ud1(root*2+1, m+1, stdr, l, r, k);
    st[root].v=(st[root*2].v+st[root*2+1].v)%p;
    return ;
}
//update2,加法,和乘法同理
void ud2(int root, int stdl, int stdr, int l, int r, long long k){
    if(r<stdl || stdr<l){
        return ;
    }
    if(l<=stdl && stdr<=r){
        st[root].add=(st[root].add+k)%p;
        st[root].v=(st[root].v+k*(stdr-stdl+1))%p;
        return ;
    }
    pushdown(root, stdl, stdr);
    int m=(stdl+stdr)/2;
    ud2(root*2, stdl, m, l, r, k);
    ud2(root*2+1, m+1, stdr, l, r, k);
    st[root].v=(st[root*2].v+st[root*2+1].v)%p;
    return ;
}
//访问,和update一样
long long query(int root, int stdl, int stdr, int l, int r){
    if(r<stdl || stdr<l){
        return 0;
    }
    if(l<=stdl && stdr<=r){
        return st[root].v;
    }
    pushdown(root, stdl, stdr);
    int m=(stdl+stdr)/2;
    return (query(root*2, stdl, m, l, r)+query(root*2+1, m+1, stdr, l, r))%p;
}
int main(){
    int n, m;
    scanf("%d%d%d", &n, &m, &p);
    for(int i=1; i<=n; i++){
        scanf("%lld", &a[i]);
    }
    bt(1, 1, n);
    while(m--){
        int chk;
        scanf("%d", &chk);
        int x, y;
        long long k;
        if(chk==1){
            scanf("%d%d%lld", &x, &y, &k);
            ud1(1, 1, n, x, y, k);
        }
        else if(chk==2){
            scanf("%d%d%lld", &x, &y, &k);
            ud2(1, 1, n, x, y, k);
        }
        else{
            scanf("%d%d", &x, &y);
            printf("%lld\n", query(1, 1, n, x, y));
        }
    }
    return 0;
}
  • 14
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

寒商

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值