线段树

基本知识

假设有编号从1到n的n个点,每一个点都存了一些信息,用[L,R]表示下标从L到R的区间信息。

1、 线段树原理

将【1,n】分解成若干特定的子区间(数量不超过4*n),然后,将每一个区间【L,R】都分解为少量特定的子区间,通过对这些少量子区间的修改或者统计,来实现快速对【L,R】的修改或者统计。(分解=二分)

线段树,又称为区间数,是一棵二叉树,而且是平衡二叉树,但不是完全二叉树。

2、性质特点

  1. 每个区间的长度是区间内整数的个数
  2. 叶子结点长度为1,不能再分
  3. 若一个结点对应的区间是【a,b】,则其子区间对应的结点分别是【a,(a+b)/2】和【(a+b)/2+1,b】;
  4. 线段树的高度是:log2(b-a+1)+1
  5. 线段树把区间上的任意一条线段都分成不超过2longn条

3、定义

#define maxn 100008
int SegTree[maxn<<2];  //元素纵膈数
//int Lazy[maxn<<2];
int A[maxn]; //原始数组
#define maxn 100007// 元素总个数
int A[maxn]; //原始数组,不一定要求用
struct SegTreeNode
{
	int val; // 结点值,如区间最大值
	int lazy; // 懒惰标记 ,延迟更新标记
	// 根据题目增加需要的元素
}SegTree[maxn<<2];

4、构造

void PushUp(int rt) //更新结点信息,这里以求和为例
{
	SegTree[rt].val = SegTree[rt<<1].val+SegTree[rt<<1|1].val;
	// SegTree[rt].val = SegTree[rt*2].val+SegTree[rt*2+1].val;
}
void build(int l,int r,int rt)
{   
     SegTree[rt].lazy = 0;
	if(l==r)  // 叶子结点
	{
		SegTree[rt].val = A[l];
		return;  
	}
	int m = (l+r)/2;
	build(l,m,rt*2); // 递归构造左子树
	build(m+1,r,rt*2+1); // 递归构造右子树
	PushUp(rt); 回溯向上更新
}

5、单点更新

//l,r 表示当前结点区间,rt表示当前线段树的根节点编号
// 实现A[L]+=C
void update(int L,int C,int l,int r,int rt)
{
	if(l==r)
	{
		SegTree[rt].val +=C;
		return;
	}
	int m = (l+r)/2;
	if(L <= m )
	update(L,C,l,m,rt,rt*2);
	else update(L,C,m+1,r,rt*2+1);
	PushUp(rt);
}

含有lazy A【L,R】+= C

void update(int L,int R,int C,int l,int r,int rt)
{
	if(L<=l&&r<=R)
	{
		SegTree[rt].val+=C*(r-l+1); // 更新数字和,向上保持正确
		SegTree[rt].lazy += C; //增加还是赋值,看需求
		return;
	}
	int m = (r+l)/2;
	PushDown(rt,m-l+1,r-m); // 下推以后才准确更新子节点
	if(L<=m) update(L,R,C,l,m,rt<<1);
	if(R>m) update(L,R,C,m+1,r,rt<<1|1);
	PushUp(rt); // 更新本节点信息
}
// ln,rn分别表示左子树和右子树的区间大小
void PushDown(int rt,int ln,int rn)
{
	if(SegTree[rt].lazy){ // 更新左右孩子
		SegTree[rt<<1].lazy += SegTree[rt].lazy; // *2
		SegTree[rt<<1].lazy += SegTree[rt].lazy;
		SegTree[rt<<1].val += SegTree[rt].lazy*ln; // 左区间
		SegTree[rt<<1|1].val += SegTree[rt].lazy*rn; // 右区间
		SegTree[rt].lazy =0; // 自己清空
	}
}

6、查询区间和

询问A【L…R】的和

// 【L,R】表示操作区间,【l,r】表示当前区间,rt表示当前结点编号
int Query(int L,int R,int l,int r,int rt)
{
	if(L<=l&&r<=R)
	{
		return SegTree[rt].val;
	}
	if(r<L||l>R) return 0; // 完全不包含此区间
	//int m = (l+r)/2;
	//int ans=0;
	//if(L<=m) ans += Query(L,R,l,m,rt*2);
	//else ans += Query(L,R,m+1,r,rt*2+1);
	//return ans;
	return Query(L,R,l,m,rt<<1)+Query(L,R,m+1,r,rt<<1|1);
}

含lazy的区间查询

int Query(int L,int R,int l,int r,int rt)
{
	if(L<=l&&r<=R)
	 return SegTree[rt].val;
	 if(L>r||R<l)
	 return 0;
	 int m = (r+l)<<1;
	 PushDown(rt,m-l+1,r-m); // 下推以后,才可以查询。
	 return Query(L,R,l,m,rt<<1)+Query(L,R,m+1,r,rt<<1);
}

7、区间更新

因为叶子结点回应系那个其相应的非叶子结点,所以一次性更新需要o(n),费时间!
为此引入延迟标记。每个结点新增加一个状态(懒惰标记),结构体增加成员,记录是否进行某种修改。我们先按照区间查询的方式将其划分成线段树中的结点,然后修改这些结点的信息,并给这些节底单标记上嗲表这种修改操作的标记。
懒惰原则:可以懒惰但是不可以出错
懒惰理由,多次更新,一次下推,无需要,不下推

总结代码:

#include <bits/stdc++.h>
using namespace std;
struct node
{
	int val,len,lazy,l,r;
}tree[4000010];
void build(int root,int l,int r)
{
	int m;
	tree[root].lazy =0;
	tree[root].l=l;
	tree[root].r=r;
	tree[root].len=r-l+1;
	if(l==r) 
	 tree[root].val=1;
	 else 
	 {
	 	m = (l+r)/2;
	 	build(root*2,l,m);
	 	build(root*2+1,m+1,r);
	 	tree[root].val = tree[root*2].val + tree[root*2+l].val;
	 }
}
void pushdown(int root)
{
	if(tree[root].lazy)
	{
		tree[root*2].lazy = tree[root].lazy;
		tree[root*2+1].lazy = tree[root].lazy;
		tree[root*2].val = tree[root*2].len * tree[root].lazy;
		tree[root*2+1].val = tree[root*2+1].len*tree[root].lazy;
		tree[root].lazy=0;
	}	
}
void update(int root,int l,int r,int addval)
{
	if(tree[root].l>=l&&tree[root].r<=r)
	{
		tree[root].lazy = addval;
		tree[root].val = tree[root].len*addval;
		return;
	}
	if(tree[root].l>r||tree[root].r<l)
	return;
	if(tree[root].lazy) pushdown(root);
	update(root*2,l,r,addval);
	update(root*2+1,l,r,addval);
	tree[root].val = (tree[root*2].val+tree[root*2+1].val);
}

例题练习

1、张煊的金箍棒(2)

#include <bits/stdc++.h>
using namespace std;

struct node
{
    int add,l,r,sum;
} t[100004];
int n,m;
void spread(int p)
{
    if(t[p].add)
    {
        t[ls(p)].add=t[rs(p)].add=t[p].add;
        t[ls(p)].sum=t[p].add*(t[ls(p)].r-t[ls(p)].l+1);
        t[rs(p)].sum=t[p].add*(t[rs(p)].r-t[rs(p)].l+1);
        t[p].add=0;
    }
}
void build(int p,int l,int r)
{
    t[p].l=l,t[p].r=r;
    if(l==r)
    {
        t[p].add=0;
        t[p].sum=1;
        return;
    }
    int mid=(l+r)>>1;
    bulid(ls(p),l,mid);
    bulid(rs(p),mid+1,r);
    t[p].sum=t[ls(p)].sum+t[rs(p)].sum;
    t[p].add=0;
}
void update(int p,int l,int r,int d)
{
    if(l<=t[p].l&&t[p].r<=r)
    {
        t[p].sum=(t[p].r-t[p].l+1)*d;
        t[p].add=d;
        return;
    }
    spread(p);
    int mid=(t[p].l+t[p].r)>>1;
    if(l<=mid)  update(ls(p),l,r,d);
    if(mid<r)  update(rs(p),l,r,d);
    t[p].sum=t[ls(p)].sum+t[rs(p)].sum;
}
int ask(int p,int l,int r)
{
    if(l<=t[p].l&&t[p].r<=r)  return t[p].sum;
    spread(p);
    int mid=(t[p].l+t[p].r)>>1;
    int ans=0;
    if(l<=mid)  ans+=ask(ls(p),l,r);
    if(mid<r)   ans+=ask(rs(p),l,r);
    return ans;
}

int main()
{
    int T=1;
    cin>>T;
    for(int i=1; i<=T; i++)
    {
        cin>>n>>m;
        bulid(1,1,n);
        while(m--)
        {
            int x,y,z;
            scanf("%lld%lld%lld",&x,&y,&z);
            update(1,x,y,z);
        }
        cout<<ask(1,1,n)<<endl;
    }
    return 0;
}

2、I hate It

#include <iostream>
#include <cstring>
using namespace std;
const int maxn=200005;
int s[maxn],mx[maxn*4];
void pushup(int rt)
{
    mx[rt]=max(mx[rt*2],mx[rt*2+1]);   //求最大值
}
void build(int l,int r,int rt)
{
    if(l==r)
    {
        mx[rt]=s[l];
        return ;
    }
    int m=(l+r)/2;
    build(l,m,rt*2);
    build(m+1,r,rt*2+1);
    pushup(rt);
}
int Query(int L,int R,int l,int r,int rt)
{
    if(L<=l && R>=r) return mx[rt];
    int m=(l+r)/2;
    int ret=0;
    if(L<=m)  ret=max(ret,Query(L,R,l,m,rt*2));
    if(R>m)   ret=max(ret,Query(L,R,m+1,r,rt*2+1));
    return ret;
}
void update(int L,int s,int l,int r,int rt)
{
    if(l==r)
    {
        mx[rt]=s;
        return ;
    }
    int m=(l+r)/2;
    if(L<=m)  update(L,s,l,m,rt*2);
    else        update(L,s,m+1,r,rt*2+1);
    pushup(rt);
}
int main()
{
    int n,m;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        for(int i=1; i<=n; i++)  scanf("%d",&s[i]);
        build(1,n,1);
        int a,b;
        char ch;
        while(m--)
        {
            scanf(" %c%d%d",&ch,&a,&b);
            if(ch=='Q')  printf("%d\n",Query(a,b,1,n,1));
            else         update(a,b,1,n,1);
        }
    }
    return 0;
}

3、LCIS

#include<bits/stdc++.h>
using namespace std;


const int MAXN = 100010;
const int MAXM = 1000010;
const int inf = 0x3f3f3f3f;

struct Tree{
    int l, r, len;
    int lv, rv;
    int lsum, rsum;
    int sum;
}tree[MAXN<<2];

void Up(int p){
    tree[p].lv=tree[p<<1].lv;
    tree[p].rv=tree[p<<1|1].rv;
    tree[p].lsum=tree[p<<1].lsum;
    tree[p].rsum=tree[p<<1|1].rsum;
    tree[p].sum=max(tree[p<<1].sum,tree[p<<1|1].sum);
    if(tree[p<<1].rv<tree[p<<1|1].lv){
        if(tree[p<<1].lsum==tree[p<<1].len)
            tree[p].lsum+=tree[p<<1|1].lsum;
        if(tree[p<<1|1].rsum==tree[p<<1|1].len)
            tree[p].rsum+=tree[p<<1].rsum;
        tree[p].sum=max(tree[p].sum,max(tree[p].lsum,tree[p].rsum));
        tree[p].sum=max(tree[p].sum,tree[p<<1].rsum+tree[p<<1|1].lsum);
    }
}
void Build(int p,int le,int ri){
    tree[p].l=le;tree[p].r=ri;tree[p].len=ri-le+1;
    if(le==ri){
        int val;
        scanf("%d",&val);
        tree[p].lv=tree[p].rv=val;
        tree[p].lsum=tree[p].rsum=tree[p].sum=1;
        return ;
    }
    int mid=(tree[p].l+tree[p].r)>>1;
    Build(p<<1,le,mid);
    Build(p<<1|1,mid+1,ri);
    Up(p);
}
void UpDate(int p,int pos,int val){
    if(tree[p].l==tree[p].r){
        tree[p].lv=tree[p].rv=val;
        return ;
    }
    int mid=(tree[p].l+tree[p].r)>>1;
    if(pos<=mid) UpDate(p<<1,pos,val);
    else UpDate(p<<1|1,pos,val);
    Up(p);
}
int Query(int p,int le,int ri){
    if(le<=tree[p].l&&tree[p].r<=ri) {
        return tree[p].sum;
    }
    int mid=(tree[p].l+tree[p].r)>>1;
    if(ri<=mid) return Query(p<<1,le,ri);
    else if(le>mid) return Query(p<<1|1,le,ri);
    else {
        int a=Query(p<<1,le,mid);
        int b=Query(p<<1|1,mid+1,ri);
        int ans=max(a,b);
        if(tree[p<<1].rv<tree[p<<1|1].lv) {
            ans=max(ans,min(tree[p<<1].rsum,mid-le+1)+min(tree[p<<1|1].lsum,ri-mid));
        }
        return ans;
    }
}
int main(){
    int T;scanf("%d",&T);
    while(T--){
        int n,q;
        scanf("%d%d",&n,&q);
        Build(1,0,n-1);
        char op[5];
        while(q--){
            int a,b;
            scanf("%s%d%d",op,&a,&b);
            if(op[0]=='U') UpDate(1,a,b);
            else printf("%d\n",Query(1,a,b));
        }
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C语言是一种广泛使用的编程语言,它具有高效、灵活、可移植性强等特点,被广泛应用于操作系统、嵌入式系统、数据库、编译器等领域的开发。C语言的基本语法包括变量、数据类型、运算符、控制结构(如if语句、循环语句等)、函数、指针等。在编写C程序时,需要注意变量的声明和定义、指针的使用、内存的分配与释放等问题。C语言中常用的数据结构包括: 1. 数组:一种存储同类型数据的结构,可以进行索引访问和修改。 2. 链表:一种存储不同类型数据的结构,每个节点包含数据和指向下一个节点的指针。 3. 栈:一种后进先出(LIFO)的数据结构,可以通过压入(push)和弹出(pop)操作进行数据的存储和取出。 4. 队列:一种先进先出(FIFO)的数据结构,可以通过入队(enqueue)和出队(dequeue)操作进行数据的存储和取出。 5. 树:一种存储具有父子关系的数据结构,可以通过中序遍历、前序遍历和后序遍历等方式进行数据的访问和修改。 6. 图:一种存储具有节点和边关系的数据结构,可以通过广度优先搜索、深度优先搜索等方式进行数据的访问和修改。 这些数据结构在C语言中都有相应的实现方式,可以应用于各种不同的场景。C语言中的各种数据结构都有其优缺点,下面列举一些常见的数据结构的优缺点: 数组: 优点:访问和修改元素的速度非常快,适用于需要频繁读取和修改数据的场合。 缺点:数组的长度是固定的,不适合存储大小不固定的动态数据,另外数组在内存中是连续分配的,当数组较大时可能会导致内存碎片化。 链表: 优点:可以方便地插入和删除元素,适用于需要频繁插入和删除数据的场合。 缺点:访问和修改元素的速度相对较慢,因为需要遍历链表找到指定的节点。 栈: 优点:后进先出(LIFO)的特性使得栈在处理递归和括号匹配等问题时非常方便。 缺点:栈的空间有限,当数据量较大时可能会导致栈溢出。 队列: 优点:先进先出(FIFO)的特性使得

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值