线段树

概念

用来维护区间信息的数据结构,可以在 O ( l o g N ) O(logN) O(logN)的时间复杂度内,实现单点修改,区间修改,区间查询等操作

1. 线段树的定义

//一般表示
#define MAXX 100007
int SegTree[MAXX<<2];
int Lazy[MAXX<<2];  //需要什么就增加相应的数组
int a[MAXX];  //原始数组
//结构体数组
#define MAXX 100007
int a[MAXX];
struct SegTreeNode
{
	int val; //节点值
	int lazy; 
	//需要什么在结构体中增加相应的元素
}SegTree[MAXX<<2];

2. 线段树的建立

数组[1,2,3,4,5]的线段树
在这里插入图片描述
线段树至少要开到原始数组的4倍(即:MAXX<<2),因为是一个树,要不断增加

//递归进行
void build(int l, int r, int p)  //对[l,r]区间建立线段树,当前根的编号为p
{
    if (l == r) // 到达叶子节点
        tree[p] = a[l]; // 用数组中的数据赋值
    else
    {
        int mid = (l + r) / 2;
        build(l, mid, p * 2); // 先建立左右子节点  //一棵树左孩子是*2,右孩子*2+1
        build(mid + 1, r, p * 2 + 1);
        tree[p] = tree[p * 2] + tree[p * 2 + 1]; // 该节点的值等于左右子节点之和
    }
}

递归建树过程:
在这里插入图片描述

2.单点修改,区间查询

如上图中的例子,想要知道区间[2,4]的值,可以从根节点开始查询,有三种情况:

  • 无交集:不搭理
  • 相交:左右哪边与目标区间有交集,搜索哪一边
  • 包含:返回这个区间的值
    在这里插入图片描述
int search(int i,int l,int r)
{
    //如果这个区间被完全包括在目标区间里面,直接返回这个区间的值
    //全=
    if(tree[i].l>=l && tree[i].r<=r)
        return tree[i].sum;
    //如果这个区间和目标区间毫不相干,返回0
    //无=
    if(tree[i].r<l || tree[i].l>r)  
    	return 0;
    //相交
    int s=0;
    if(tree[i*2].r>=l)  
    	s+=search(i*2,l,r);//如果这个区间的左儿子和目标区间又交集,那么搜索左儿子
    if(tree[i*2+1].l<=r)  
    	s+=search(i*2+1,l,r);//如果这个区间的右儿子和目标区间又交集,那么搜索右儿子
    return s;
}

当更改一点的数值时

void add(int i,int dis,int k)
{
    if(tree[i].l==tree[i].r)
    {//如果是叶子节点,那么说明找到了
        tree[i].sum+=k;
        return ;
    }
    if(dis<=tree[i*2].r)  
    	add(i*2,dis,k);//在哪往哪跑
    else  
    	add(i*2+1,dis,k);
    tree[i].sum=tree[i*2].sum+tree[i*2+1].sum;//返回更新
    return ;
}

在这里插入图片描述

3.区间修改+区间查询

如果要修改[1,1000000]区间,就要把里面的所有区间都修改一次,费时费力, 懒人标记法 随之诞生~
修改的时候,假设给每个数+k

  • ① 如果是包含关系,不用到最底下了,只需知道有多少个数n,然后正在原来数字的基础上+nk,即可
  • ② 如果是相交的关系,就下传懒人标记,直到是包含的关系
void add(int i,int l,int r,int k)
{
    if(tree[i].r<=r && tree[i].l>=l)//如果当前区间被完全覆盖在目标区间里
    {
        tree[i].sum+=k*(tree[i].r-tree[i].l+1);
        tree[i].lz+=k;//记录lazytage
        return ;
    }
    push_down(i);//向下传递
    if(tree[i*2].r>=l)
        add(i*2,l,r,k);
    if(tree[i*2+1].l<=r)
        add(i*2+1,l,r,k);
    tree[i].sum=tree[i*2].sum+tree[i*2+1].sum;
    return ;
}

其中的pushdown,就是把自己的lazytage归零,并给自己的儿子加上,并让自己的儿子加上k*(r-l+1)

void push_down(int i)
{
    if(tree[i].lz!=0)
    {
        tree[i*2].lz+=tree[i].lz;//左右儿子分别加上父亲的lz
        tree[i*2+1].lz+=tree[i].lz;
        init mid=(tree[i].l+tree[i].r)/2;
        tree[i*2].data+=tree[i].lz*(mid-tree[i*2].l+1);//左右分别求和加起来
        tree[i*2+1].data+=tree[i].lz*(tree[i*2+1].r-mid);
        tree[i].lz=0;//父亲lz归零
    }
    return ;
}

debug一次就理解了…

查询

int search(int i,int l,int r){
    if(tree[i].l>=l && tree[i].r<=r)
        return tree[i].sum;
    if(tree[i].r<l || tree[i].l>r)  
    	return 0;
    push_down(i);
    int s=0;
    if(tree[i*2].r>=l)  
    	s+=search(i*2,l,r);
    if(tree[i*2+1].l<=r)  
    	s+=search(i*2+1,l,r);
    return s;
}

题目( •̀ ω •́ )✧

1.模板题,洛谷
2.求逆序数

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值