线段树(适用于初学者)

线段树

一:一般功能
二:基本概念
三:基本操作
一:基本功能
线段树的一般功能为定点修改,区间修改,区间求和,求区间最大值等操作。当然也有一些扩展的,例如主席树一类的。不过关于扩展我还不太了解,现在先讲一下一般功能,日后再更新。
二:基本概念
1:它是一个二叉树结构
2:它的每一个节点存一个结构体,每个结构体一般包括它的左右边界,还有它的权值一类的,还要看题具体定
3:它用到的思想为二分
4:它的结构如下图
例如第一个节点包含区间1-10的信息,如果是求区间和,那么就是存的1-10的所有值的和,它又可以分为两个左右区间,知道当它的左右边界相等时便是一个数据。
线段树三:基本操作
1:定义子节点结构体

在这里插入代码片struct node
{
    int l, r, w, flag;//其中l,r表示这个区间的左右边界,w表示这个区间的权值,flag表示如要改变这个区间,它就是改变的值
    int dis() { return r - l + 1; }//这点所代表区间的个数
    int mid() { return (r + l) / 2; }//区间的中间值,便于将这个区间分割
} a[maxn * 4];

2:建树

在这里插入代码片void build(int k, int l, int r)\\建树,就是向每一个子节点里赋值
{ //当前节点的区间
    a[k] = {l, r, 0, 0};//对结构体赋值
    if (l == r)
    {
        cin >> a[k].w;//当这是一个点时,赋值
        return;
    }
    build(k << 1, l, a[k].mid());//将区间往下分
    build(k << 1 | 1, a[k].mid() + 1, r);
    a[k].w = a[k << 1].w + a[k << 1 | 1].w;//一个区间和等于左边的加右边的
}

3:区间修改,也可用于单点修改

void down(int k)//主要是将这个区间改变的值向下分
{
    a[k << 1].w += a[k << 1].dis() * a[k].flag;
    a[k << 1 | 1].w += a[k << 1 | 1].dis() * a[k].flag;
    a[k << 1].flag += a[k].flag;
    a[k << 1 | 1].flag += a[k].flag;
    a[k].flag = 0;
}

void update(int k, int l, int r, int w)
{ // 要更新的总区间.(l,r)不变
    if (a[k].l >= l && a[k].r <= r)//这句主要判断你要改变的区间是否大于你查询的这个,如果大于,直接更改区间即可
    {
        a[k].w += w * a[k].dis();//将这个区间加一个数,如果直接改为一个数,直接写等于即可。
        a[k].flag += w;
        return;
    }
    if (a[k].flag)
        down(k);
    if (a[k].mid() >= l)
        update(k << 1, l, r, w);
    if (a[k].mid() < r)
        update(k << 1 | 1, l, r, w);
    a[k].w = a[k << 1].w + a[k << 1 | 1].w;
}

4:区间求和

int query(int k, int l, int r)//求区间的和
{
    if (a[k].l >= l && a[k].r <= r)
        return a[k].w;
    if (a[k].flag)//这句是判断这个区间是否被改变过,如果改变过,应该将改变的值下方到区间的每一个元素
        down(k);
    int sum = 0;
    if (a[k].mid() >= l)//这主要是缩小区间去寻找满足条件的区间
        sum += query(k << 1, l, r);
    if (a[k].mid() < r)
        sum += query(k << 1 | 1, l, r);
    a[k].w = a[k << 1].w + a[k << 1 | 1].w;
    return sum;
}

5:全部代码
包括建树,区间求和,区间修改,单点修改,如果要去区间的最大值只需要稍微修改一下,将每一个数存的不是区间的和而是存区间的最大值

#include <bits/stdc++.h>

using namespace std;
#define mem(a, b) memset(a, b, sizeof a)
#define IN freopen("in.txt", "r", stdin)
#define DEBUG(a) cout << (a) << endl

typedef long long ll;
int dir8[8][2] = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}, {1, 1}, {1, -1}, {-1, 1}, {-1, -1}};
int dir4[4][2] = {1, 0, 0, 1, -1, 0, 0, -1};
const int INF = 0x3f3f3f3f;
int mod = 1e9 + 7;
const int maxn = 1e5 + 10;

struct node
{
    int l, r, w, flag;
    int dis() { return r - l + 1; }
    int mid() { return (r + l) / 2; }
} a[maxn * 4];

void build(int k, int l, int r)
{ //当前节点的区间
    a[k] = {l, r, 0, 0};
    if (l == r)
    {
        cin >> a[k].w;
        return;
    }
    build(k << 1, l, a[k].mid());
    build(k << 1 | 1, a[k].mid() + 1, r);
    a[k].w = a[k << 1].w + a[k << 1 | 1].w;
}
void down(int k)
{
    a[k << 1].w += a[k << 1].dis() * a[k].flag;
    a[k << 1 | 1].w += a[k << 1 | 1].dis() * a[k].flag;
    a[k << 1].flag += a[k].flag;
    a[k << 1 | 1].flag += a[k].flag;
    a[k].flag = 0;
}

void update(int k, int l, int r, int w)
{ // 要更新的总区间.(l,r)不变
    if (a[k].l >= l && a[k].r <= r)
    {
        a[k].w += w * a[k].dis();
        a[k].flag += w;
        return;
    }
    if (a[k].flag)
        down(k);
    if (a[k].mid() >= l)
        update(k << 1, l, r, w);
    if (a[k].mid() < r)
        update(k << 1 | 1, l, r, w);
    a[k].w = a[k << 1].w + a[k << 1 | 1].w;
}

int query(int k, int l, int r)
{
    if (a[k].l >= l && a[k].r <= r)
        return a[k].w;
    if (a[k].flag)
        down(k);
    int sum = 0;
    if (a[k].mid() >= l)
        sum += query(k << 1, l, r);
    if (a[k].mid() < r)
        sum += query(k << 1 | 1, l, r);
    a[k].w = a[k << 1].w + a[k << 1 | 1].w;
    return sum;
}

int main()
{

    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n, m;
    cin >> n >> m;
    build(1, 1, n);
    while (m--)
    {
        int func;
        int l, r, w;
        cin >> func;
        if (func == 1)
        {
            cin >> l >> r >> w;
            update(1, l, r, w);
        }
        else if (func == 2)
        {
            cin >> l >> r;
            cout << query(1, l, r) << endl;
        }

    }

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值