学习笔记-线段树(一)(21.8.5+8.7)

一、线段树
简单说就是用二叉树来构造,树的每一个结点代表一条线段[L,R],用于区间处理。
如果L=R,说明这个结点只有一个点,是叶子结点;如果L<R,说明不止一个点,左儿子[L,M],右儿子[M+1,R],其中M=(L+R)/2。
线段树中,如果一个节点的编号为x,那么左儿子的编号为2x,右儿子的编号为2x+1。
N个元素的线段树的高度为logN+1。
二、建树
普通二叉树建线段树

const int Max=10000;
struct {
    
    
    int l, r, len;//len储存这个区间下数字的个数
} tree[4*Max];//线段树空间需要
void BuildTree(int left, int right, int u)//建树
{
    
    
    tree[u].l=left;
    tree[u].r=right;
    tree[u].len=right-left+1;//更新结点u的值
    if(left==right)
        return;
    BuildTree(left,(left+right)>>1,u<<1);//递归左子树
    BuildTree(((left+right)>>1)+1,right,(u<<1)+1);//递归右子树
}

完全二叉树建立线段树

void BuildTree(int n,int last_left)//用完全二叉树建一个线段树
{
    
    
    int i;
    for(i=last_left; i<last_left+n; i++)
        tree[i]=1;//给二叉树的最后一行赋值,左边n个结点是n头牛
    while(last_left!=1)//从二叉树的最后一行倒推到根结点,根结点的值是总数
    {
    
    
        for(i=last_left/2; i<last_left; i++)
            tree[i]=tree[i*2]+tree[i*2+1];
        last_left=last_left/2;
    }
}

建树模板

#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1//这里或运算即rt*2+1
void build(int l,int r,int rt)//满二叉树建树
{
    
    
    if(l==r)
    {
    
    
        scanf("%lld",&tree[rt]);
        return;
    }
    int mid=(l+r)>>1;
    build(lson);
    build(rson);
    tree[rt]=tree[rt<<1]+tree[rt<<1|1];
}

关于以上结构体数组的范围要乘4,因为线段树空间需要。
三、单点修改
模板:d为更新值,index为更新点,lr为更新范围

void update(int d,int index,int l,int r,int node){
    
    
	if(l == r) {
    
    
		tree[node] += d; // 更新方式,可以变化
		return;
	}
	int mid = (l+r) / 2;
	// push_down(node,mid-l+1,r-mid); 若既有点更新又有区间更新,需要这句话
	if(index <= mid){
    
    
		update(d,index,l,mid,node*2);
	}else{
    
    
		update(d,index,mid+1,r,node*2+1);
	}
	tree[node] = tree[node*2] + tree[node*2 + 1]; // 向上更新
}

依然是学习书上例题Lost Cows
题意:编号1~n的数字乱序排列,每个位置的数字知道前面比它小的数字个数,求乱序数列。
例子:在这里插入图片描述
由图可知,需要在剩下的编号里找到第pre[n]+1大的数,即为ans[n]
•普通二叉树建立线段树
在这里插入图片描述
线段树求解本题,每个结点表示区间内数字的个数,向左找的过程,实际上是缩小找第pre[n]+1大的数的范围,向右找是找去掉左边的个数m后也就是找第pre[n]+1-m大的数,所以每向右找一次区间个数都要变化,进一步找到ans[n]。
代码实现步骤:建树->查询+更新(分两种情况,找左或找右,递归)->得序列

#include<iostream>
#include<cstdio>
using namespace std;
const int Max=10000;
struct {
    
    
    int l, r, len;//len储存这个区间下数字的个数,即这个结点下牛的数量
} tree[4*Max];//线段树空间需要
int pre[Max], ans[Max];
void BuildTree(int left, int right, int u)//建树
{
    
    
    tree[u].l=left;
    tree[u].r=right;
    tree[u].len=right-left+1;//更新结点u的值
    if(left==right)
        return;
    BuildTree(left,(left+right)>>1,u<<1);//递归左子树
    BuildTree(((left+right)>>1)+1,right,(u<<1)+1);//递归右子树
}
int query(int u,int num)//查询+维护,所求值为当前区间中左起第num个元素 
{
    
    
    tree[u].len--;//对访问到的区间维护len,即把这个结点上牛的数量减1
    if(tree[u].l==tree[u].r)
        return tree[u].l;
//情况1:左子区间内牛的个数不够,则查询右子区间中左起第nun-tree(u << 1]. len个元素
    if(tree[u<<1].len < num)
        return query( (u<<1) +1, num- tree[u<< 1]. len);
//情况2:左子区间内牛的个数足够,依旧查询左子区间中左起第num个元素
    if (tree[u<<1].len >= num)
        return query(u << 1, num);
}
int main()
{
    
    
    int n, i;
    scanf("%d", &n);
    pre[1] =0;
    for(i =2; i<=n; i++)
        scanf("%d", &pre[i]);
    BuildTree(1, n, 1);
    for(i= n; i>=1; i--)
        ans[i] = query(1, pre[i]+1);
//从后往前推断出每次最后一个数字
    for(i =1; i<=n; i++)
        printf("%d\n", ans[i]);
    return 0;
}

完全二叉树建立线段树
这种方式更好理解,每次只需要数最后一行第几大数就可以,图解如下:在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
const int Max=10000;
int pre[Max]= {
    
    0},tree[4*Max]= {
    
    0},ans[Max]= {
    
    0};
void BuildTree(int n,int last_left)//用完全二叉树建一个线段树
{
    
    
    int i;
    for(i=last_left; i<last_left+n; i++)
        tree[i]=1;//给二叉树的最后一行赋值,左边n个结点是n头牛
    while(last_left!=1)//从二叉树的最后一行倒推到根结点,根结点的值是牛的总数
    {
    
    
        for(i=last_left/2; i<last_left; i++)
            tree[i]=tree[i*2]+tree[i*2+1];
        last_left=last_left/2;
    }
}
int query(int u,int num,int last_left)
{
    
    //u表示结点,num表示剩余序列里第pre[i]+1大的数,用来找ans[i],last_left表示最后一行左边第一个结点的标号
//查询+维护,关键的一点是所求值为当前区间中左起第num个元素m
    tree[u]--;//对访问到的区间维护剩下的牛的个数m
    if(tree[u]==0&&u>=last_left)
        return u;
//情况1:找右
    if(tree[u<<1]<num)
        return query((u<<1)+1,num-tree[u<<1],last_left);
//情况2:找左
    if(tree[u<<1]>=num)
        return query(u<<1,num,last_left);
}
int main()
{
    
    
    int n,last_left;//last_left表示最后一行左边第一个结点的标号
    scanf("%d",&n);
    pre[1]=0;
    last_left=1<<(int(log(n)/log(2))+1);//二叉树最后一行的最左边一个数的计算方法是找离n最近的2的指数,例如3->4, 4->4, 5->8
    for(int i=2; i<=n; i++)
        scanf("%d",&pre[i]);
    BuildTree(n,last_left);//n=5个数,last_left=8
    for(int i=n; i>=1; i--)//从后往前推断出每次最后一个数字
        ans[i]=query(1,pre[i]+1,last_left)-last_left+1;//每次都从根结点开始查起
    for(int i=1; i<=n; i++)
        printf("%d\n", ans[i]);
    return 0;
}

学以致用:Mayor’s posters:线段树+离散化
题意:在长 10000000 的墙上贴海报,告诉你海报数,每张海报的要贴的区间,按照给出的顺序贴,求最后有几张海报能露出来。
在这里插入图片描述
思路:•离散化:测试样例范围过大,先压缩区间
在这里插入图片描述
未完…

おすすめ

転載: blog.csdn.net/weixin_51443397/article/details/119424011