线段树

本文详细介绍了线段树数据结构,主要用于区间修改和查询,具有O(log n)的时间复杂度。内容包括线段树的定义、建树过程、单点修改、区间修改及查询操作,并给出了HDU1166和POJ 3468两个例题进行实战解析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本博客同步更新至 https://startcraft.cn

介绍

线段树的应用场景在区间修改和区间查询上,修改和查询的时间复杂度都为O(log n)
线段树如它的名字一样将区间分成一段一段的,如123456789这一串数字,以区间求和为例

上图就是构建的线段树,其中结点里的值是该结点表示的区间的和,中括号里的数字代表表示的区间

实现(以区间求和为例)

定义

线段树的根结点定义为1,那么左子节点就是n*2,右子节点是n*+1
通常2*n在代码中写成 n<<1 。 2*n+1写成 n<<1|1 。
线段树所需的空间
足够的空间 = 数组大小n的四倍。
实际上足够的空间 = (n向上扩充到最近的2的某个次方)的两倍。

const int maxn = 100005;//元素的个数
ll tree[maxn << 2];//线段树的结点,大小开元素的4倍
ll add[maxn << 2];//标记数组(懒惰标记)区间修改会用到

建树

void push_up(int id)//根据子节点来更新父节点
{
    tree[id] = tree[id << 1] + tree[id << 1 | 1];
}
void build(int l, int r, int id)//l,r表示当前区间,id表示当前结点的下标
{
    if (l == r)//到叶子结点就赋值
    {
        tree[id] = num[l];
        return ;
    }
    int mid = (l + r) >> 1;
    build(l, mid, id << 1);//递归构建左子树
    build(mid + 1, r, id << 1 | 1);//递归构建右子树
    push_up(id);//当前结点的子节点已经构建完成,然后更新当前结点
}

单点修改

void update(int l, int r, int now, int number, int pos)
//l,r是当前区间,now是当前结点下标,number是要修改的数,pos是要修改的下标
{
    if (l == r)//到叶子结点直接修改
    {
        tree[now] += number;
        return ;
    }
    int mid = (l + r) >> 1;
    if (pos <= mid)//判断要修改的点在左子树还是右子树,然后递归修改
        update(l, mid, now << 1, number, pos);
    else
        update(mid + 1, r, now << 1 | 1, number, pos);
    push_up(now);//当前结点的子节点已经更新,所以更新当前结点
}

区间修改

关于区间的修改需要用到标记数组,改数组的含义是"当前结点的值已经更新,但是子节点的值未更新"
如add[7]=3,代表结点7的值已经更新,但是结点7的子节点都还没有加上这个值,这就需要一个下推标记的函数

void push_down(int id, int ln, int rn)
//id代表当前结点,ln代表左区间的元素个数,rn代表右区间的元素个数
{
    if (add[id] != 0)
    {
        tree[id << 1] += add[id] * ln;//更新左区间的值
        tree[id << 1 | 1] += add[id] * rn;//更新右区间的值
        add[id << 1] += add[id];//将标记下推,更新左区间标记
        add[id << 1 | 1] += add[id];//更新右区间标记
        add[id] = 0;//清空当前结点标记
    }
}

修改

void update(int l, int r, int L, int R, int id, int number)
//l,r,id的含义不变,L,R代表需要修改的区间,number是要加上的数
{
    if (l >= L && r <= R)//若当前区间在需要修改的区间内,直接修改
    {
        tree[id] += number * (r - l + 1);
	//这里乘以区间长度是因为区间里的所有值都要加上number
        add[id] += number;//打上标记
        return ;
    }
    int mid = (l + r) >> 1;
    push_down(id, mid - l + 1, r - mid);//下推标记
    if (mid >= L)//若需要修改的区间与左区间相交,更新左区间
        update(l, mid, L, R, id << 1, number);
    if (mid < R)//若需要修改的区间与右区间相交,更新右区间
        update(mid + 1, r, L, R, id << 1 | 1, number);
    push_up(id);//当前结点的子节点已经更新,所以更新当前结点
}

查询

int query(int l, int r, int L, int R, int id)//L,R是需要查询的区间
{
    if (l >= L && r <= R)//若当前区间在需要查询的区间内,直接返回值
    {
        return tree[id];
    }
    ll ans = 0;//累加答案
    int mid = (l + r) >> 1;
    push_down(id, mid - l + 1, r - mid);
    //可能更新中没有推完的标记,下推标记,如果只是单点修改这个可以不要
    if (mid >= L)//若需要查询的区间与左区间相交,查询左区间
        ans += query(l, mid, L, R, id << 1);
    if (mid < R)//若需要查询的区间与右区间相交,查询右区间
        ans += query(mid + 1, r, L, R, id << 1 | 1);
    return ans;
}

例题

HDU1166

单点修改的例子

#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
#define wfor(i,j,k) for(i=j;i<k;++i)
#define mfor(i,j,k) for(i=j;i>=k;--i)
// void read(int &x) {
//  char ch = getchar(); x = 0;
//  for (; ch < '0' || ch > '9'; ch = getchar());
//  for (; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
// }
const int maxn = 50005;
int tree[maxn << 2];
int num[maxn];
void push_up(int now)
{
    tree[now] = tree[now << 1] + tree[now << 1 | 1];
}
void build(int l, int r, int now)
{
    if (l == r)
    {
        tree[now] = num[l];
        return;
    }
    int mid = (l + r) >> 1;
    build(l, mid, now << 1);
    build(mid + 1, r, now << 1 | 1);
    push_up(now);
}
void update(int l, int r, int now, int number, int pos)
{
    if (l == r)
    {
        tree[now] += number;
        return ;
    }
    int mid = (l + r) >> 1;
    if (pos <= mid)
        update(l, mid, now << 1, number, pos);
    else
        update(mid + 1, r, now << 1 | 1, number, pos);
    push_up(now);
}
int query(int l, int r, int now, int L, int R)
{
    if (l >= L && r <= R)
    {
        return tree[now];
    }
    int ans = 0;
    int mid = (l + r) >> 1;
    if (mid >= L)
        ans += query(l, mid, now << 1, L, R);
    if (mid < R)
        ans += query(mid + 1, r, now << 1 | 1, L, R);
    return ans;
}
int main()
{
    std::ios::sync_with_stdio(false);
// #ifdef test
//     freopen("F:\\Desktop\\question\\in.txt", "r", stdin);
// #endif
// #ifdef ubuntu
//     freopen("/home/time/debug/debug/in", "r", stdin);
//     freopen("/home/time/debug/debug/out", "w", stdout);
// #endif
    int t;
    cin >> t;
    int casecnt = 0;
    while (t--)
    {
        casecnt++;
        cout << "Case " << casecnt << ":" << endl;
        int n;
        cin >> n;
        int i;
        wfor(i, 1, n + 1)
        {
            cin >> num[i];
        }
        build(1, n, 1);
        char op[10];
        while (cin >> op)
        {
            if (op[0] == 'A')
            {
                int number, pos;
                cin >> pos >> number;
                update(1, n, 1, number, pos);
            } else if (op[0] == 'S')
            {
                int number, pos;
                cin >> pos >> number;
                update(1, n, 1, -number, pos);
            } else if (op[0] == 'Q')
            {
                int l, r;
                cin >> l >> r;
                int ans = query(1, n, 1, l, r);
                cout << ans << endl;
            } else
            {
                break;
            }
        }
    }
    return 0;
}

POJ 3468

区间修改的例子

#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
#define wfor(i,j,k) for(i=j;i<k;++i)
#define mfor(i,j,k) for(i=j;i>=k;--i)
// void read(int &x) {
//  char ch = getchar(); x = 0;
//  for (; ch < '0' || ch > '9'; ch = getchar());
//  for (; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
// }
const int maxn = 100005;
int num[maxn];
ll tree[maxn << 2];
ll add[maxn << 2];
void push_up(int id)
{
    tree[id] = tree[id << 1] + tree[id << 1 | 1];
}
void build(int l, int r, int id)
{
    if (l == r)
    {
        tree[id] = num[l];
        return ;
    }
    int mid = (l + r) >> 1;
    build(l, mid, id << 1);
    build(mid + 1, r, id << 1 | 1);
    push_up(id);
}
void push_down(int id, int ln, int rn)
{
    if (add[id] != 0)
    {
        tree[id << 1] += add[id] * (ll)ln;
        tree[id << 1 | 1] += add[id] * (ll)rn;
        add[id << 1] += add[id];
        add[id << 1 | 1] += add[id];
        add[id] = 0;
    }
}
void update(int l, int r, int L, int R, int id, ll number)
{
    if (l >= L && r <= R)
    {
        tree[id] += number * (ll)(r - l + 1);
        add[id] += number;
        return ;
    }
    int mid = (l + r) >> 1;
    push_down(id, mid - l + 1, r - mid);
    if (mid >= L)
        update(l, mid, L, R, id << 1, number);
    if (mid < R)
        update(mid + 1, r, L, R, id << 1 | 1, number);
    push_up(id);
}
ll query(int l, int r, int L, int R, int id)
{
    if (l >= L && r <= R)
    {
        return tree[id];
    }
    ll ans = 0;
    int mid = (l + r) >> 1;
    push_down(id, mid - l + 1, r - mid);
    if (mid >= L)
        ans += query(l, mid, L, R, id << 1);
    if (mid < R)
        ans += query(mid + 1, r, L, R, id << 1 | 1);
    return ans;
}
int main()
{
    std::ios::sync_with_stdio(false);
// #ifdef test
//     freopen("F:\\Desktop\\question\\in.txt", "r", stdin);
// #endif
// #ifdef ubuntu
//     freopen("/home/time/debug/debug/in", "r", stdin);
//     freopen("/home/time/debug/debug/out", "w", stdout);
// #endif
    int n, q;
    cin >> n >> q;
    int i;
    wfor(i, 1, n + 1)
    {
        cin >> num[i];
    }
    build(1, n, 1);
    wfor(i, 0, q)
    {
        char op;
        cin >> op;
        if (op == 'Q')
        {
            int l, r;
            cin >> l >> r;
            ll ans = query(1, n, l, r, 1);
            cout << ans << endl;
        } else
        {
            int l, r;
            ll number;
            cin >> l >> r >> number;
            update(1, n, l, r, 1, number);
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值