暑假集训--线段树

线段树

  • 线段树的每个结点都代表一个区间。
  • 线段树有唯一的根节点代表整个范围,比如:[1,N];
  • 线段树的每个叶子结点都代表一个长度为1的元区间 [x,x];
  • 对于每个内部节点[l,r],它的左节点是[l,m],右节点是[m+1,r],其中m=(l+r)/2(向下取整)。
    在这里插入图片描述
    线段树的基本用途是对序列进行维护,支持查询和修改的指令

我采用了结构体的方式存储线段树的信息:

struct SegmentTree
{
    int l, r;
    int data;
} t[N * 4 + 5];
int a[N];  //原始数据数组

建树:

void build(int p,int l,int r)  //存储区间和的线段树
{
    t[p].l = l, t[p].r = r;  //节点p代表[l,r]
    if(l==r) //单点
    {
        t[p].data = a[l]; 
        return;
    }
    int m = (l + r) / 2;  //折半
    int ls = 2 * p, rs = ls + 1;
    build(ls, l, m);      //建左子树
    build(rs, m + 1, r);  //建右子树
    t[p].data = t[ls].data + t[rs].data;   //p代表的区间和 = ls代表的区间和 + rs代表的区间和
}
void build(int p,int l,int r)  //存储区间最大值的线段树
{
    t[p].l = l, t[p].r = r;
    if(l==r) 
    {
        t[p].date = a[l];
        return;
    }
    int m = (l + r) / 2;
    int ls = 2 * p, rs = ls + 1;
    build(ls, l, m);
    build(rs, m + 1, r);
    t[p].date = max(t[ls].date,t[rs].date);  //p区间的最大值 = max(ls最大值,rs最大值)
}

这里注意,ls和rs为p的两个子区间,故可以通过 t[p].date = max(t[ls].date,t[rs].date);得出p区间的最大值,但是不可以通过ls和p求出rs区间的最大值

单点修改

void add(int p,int x,int v)  //将x位置的数增加v  同样是存储区间和的线段树
{
    if(t[p].l==t[p].r)
    {
        t[p].data += v;
        return;
    }
    int m = (t[p].l + t[p].r) / 2;
    int ls = 2 * p, rs = ls + 1;
    if(x<=m)
        add(ls, x, v);
    else
        add(rs, x, v);
    t[p].data = t[ls].data + t[rs].data;
}
void add(int p,int x,int v)  //维护区间最大值的线段树  将x位置的数值改为v
{
    if(t[p].l==t[p].r)
    {
        t[p].date = v;
        return;
    }
    int m = (t[p].l + t[p].r) / 2;
    int ls = 2 * p, rs = ls + 1;
    if(x<=m)
        add(ls, x, v);
    else
        add(rs, x, v);
    t[p].date = max(t[ls].date,t[rs].date);
}

询问

int Query(int p,int l,int r)  //询问l-r区间和
{
    if(l<=t[p].l&&r>=t[p].r) //l-r完全覆盖了p代表的区间
    {
        return t[p].data;  //直接返回值
    }
    int m = (t[p].l + t[p].r) / 2;   //向下取整
    int ls = 2 * p, rs = ls + 1;
    int sum = 0;
    if(l<=m)  //p此时的区间的左半边和l-r有交集但不完全被完全覆盖
    {
        sum += Query(ls, l, m); //查询左半边 此时的l-r其实是l-m
    }
    if(r>m)   //这里没有等于
    {
        sum += Query(rs, m + 1, r);  //同理
    }
}
int Query(int p,int l,int r)  //询问区间最大值
{
    if(l<=t[p].l&&r>=t[p].r)
    {
        return t[p].date;
    }
    int m = (t[p].l + t[p].r) / 2;
    int ls = 2 * p, rs = ls + 1;
    int maxx=-inf;  //初始化最大值为-inf
    if(l<=m)
       	maxx=max(maxx,Query(ls,l,r));
    if(r>m)
        maxx=max(maxx,Query(rs,l,r));
	return maxx;
}

区间修改,区间查询

这里以洛谷的模板题举例(也可以用树状数组做,明天补上

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

#include <cmath>
#include <queue>
#include <string>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define forn(i, n) for (int i = 0; i < (n); i++)
#define forab(i, a, b) for (int i = (a); i <= (b); i++)
#define forba(i, b, a) for (int i = (b); i >= (a); i--)
#define mset(a, n) memset(a, n, sizeof(a))
#define fast ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
#define P pair<int,int>
#define fi first
#define se second
using namespace std;
#define N 100005
#define inf 0x3f3f3f3f
#define ll long long
ll ans, temp[N], x;
struct SegmentTree
{
    int l, r;
    ll data, Add;
} t[N * 4 + 5];
int n, m;
void build(int p,int l,int r)
{
    t[p].l = l;
    t[p].r = r;
    t[p].Add = 0;
    if(l==r)
        t[p].data = temp[l];
    else
    {
        int m = (l + r) / 2;
        int ls = 2 * p, rs = 2 * p + 1;
        build(ls, l, m);
        build(rs, m + 1, r);
        t[p].data = t[ls].data + t[rs].data;
    }
}
void add(int p,int l,int r,ll d)
{
    if(t[p].l==l&&t[p].r==r)
    {
        t[p].Add += d;
    }
    else
    {
        int ls = 2 * p, rs = 2 * p + 1;
        int m = t[ls].r;
        t[p].data += (r - l + 1) * d;
        if(l>m)
            add(rs, l, r, d);
        else if(r<=m)
            add(ls, l, r, d);
        else
        {
            add(ls, l, m, d);
            add(rs, m + 1, r, d);
        }
    }
}
void Query(int p,int l,int r,ll d)
{
    d += t[p].Add;
    if(t[p].l==l&&t[p].r==r)
    {
        ans += (t[p].data + d * (r - l + 1));
    }
    else
    {
        int ls = 2 * p, rs = 2 * p + 1;
        int m = t[ls].r;
        if(l>m)
            Query(rs, l, r, d);
        else if(r<=m)
            Query(ls, l, r, d);
        else
        {
            Query(ls, l, m, d);
            Query(rs, m + 1, r, d);
        }
    }
}
int main()
{
    scanf("%d%d", &n, &m);
    forab(i, 1, n) scanf("%lld", temp + i);
    build(1, 1, n);
    forab(i,1,m)
    {
        int x;
        scanf("%d", &x);
        if(x==1)
        {
            ll a, b, c;
            scanf("%lld %lld %lld", &a, &b, &c);
            add(1, a, b, c);
        }
        else
        {
            ll a, b;
            scanf("%lld %lld", &a, &b);
            ans = 0;
            Query(1, a, b, 0);
            printf("%lld\n", ans);
        }
    }
    system("pause");
}
/*

5 5
1 5 4 2 3
2 2 4
1 2 3 2
2 3 4
1 1 5 1
2 1 4*/

在增加时,如果要加的区间正好覆盖一个节点,则增加其节点的Add值,不再往下走,否则要更新data,再将增量往下传在查询时,如果待查区间不是正好覆盖一个节点,就将节点的Add往下带,然后将Add代表的所有增量累加到data上后将Inc清0,接下来再往下查询
未完。。。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值