线段树(C/C++)

本文详细介绍了线段树算法,包括其原理、数组表示的优势、递归创建过程以及单点和区间修改函数的实现,重点强调了区间修改中的懒惰标记技术。
摘要由CSDN通过智能技术生成

前言:

这几天连续没更新是因为小编一直在研究线段树算法,连续做了一个星期,小编终于搞懂了线段树里面的逻辑,线段树算是入门算法里的一个高级算法,刚入门时可能有点难度,但是仔细研究过后,逻辑就会越来越清晰。一下我来分享一下线段树的所有功能实现。

所谓线段树,其实是一种特殊的二叉树,它采用数组的方式表示一种树结构而不是用节点的方式,可能刚入门的小伙伴会疑问,数组和树是两种不同的数据结构,怎么能用数组的方式来表示树结构呢?我们娓娓道来。用数组的方式表示树结构其实就是树的静态存储方式,它受限于数组的大小,需要提前确定好数组的大小,而节点链式存储是树的动态存储,其存储大小不受限,当然静态存储时以牺牲空间来换取时间的方法。如果我们有很多数据保存在一个数组中,而我们要对这个数组进行一些操作,那么使用传统的循环来控制的话,时间复杂度太大,特别是一些算法题很容易超时,这时候线段树可以帮我们解决,将复杂度降为nlog(n),在此说明:线段树的应用通常是一些区间求和,区间修改,区间最大值、最小值等操作。

1.我们首先要做的是创建线段树,采用递归的方式来创建。

代码如下:

首先是递归方便递归树的左右儿子:

inline ll ls(int p){
    return p<<1;
}
inline ll rs(int p){
    return p<<1|1;
}
inline void buildtree(ll k, ll start, ll end){
    if(start == end){
        tree[k] = arr[start];
        return;
    };
    ll mid = (start + end) >> 1;
    buildtree(ls(k),start,mid);
    buildtree(rs(k),mid+1,end);
    tree[k] = tree[ls(k)] + tree[rs(k)];    /*回溯修改父节点*/
}

特别注意一定要回溯修改父节点,因为父节点存储的是区间的信息,比如和或者积等。

接着我们要实现更新线段树的函数,这里提供两种修改方法,一种是单点修改,一种是区间修改,区间修改引入lazy标记以便快速求值。

单点修改:

/*单点修改*/
inline void update(ll p,ll val, ll k, ll start, ll end){
    ll mid = (start + end) >> 1;
    if(start == end){
        if(p == start) {
            tree[k] = val;
            return;
        }
    }
    if(p <= mid) update(p,val,ls(k),start,mid);
    if(p > mid) update(p,val,rs(k),mid+1,end);
    tree[k] = tree[ls(k)] + tree[rs(k)];
}

区间修改:

进行区间修改时,采用lazy标记,传递修改信息。

/*lazy标记*/
inline void lazy_push_down(ll k , ll start, ll end){
    if(tag[k] != 0){
        tree[k] += (end - start + 1)*tag[k];
        if(start != end){
            tag[ls(k)] += tag[k];
            tag[rs(k)] += tag[k];
        }
        tag[k] = 0;
    }
}

区间修改:

/*区间修改*/
inline void update(ll nl, ll nr, ll val, ll k, ll start, ll end){
    lazy_push_down(k,start,end);
    if(nl <= start && nr >= end){
        tree[k] += (end - start + 1)*val;
        if(start != end){
            tag[ls(k)] += val;
            tag[rs(k)] += val;
            lazy_push_down(k,start,end);
        }
        return;
    }
    if(nl > end || nr < start) return;
    ll mid = (end + start) >> 1;
    update(nl,nr,val,ls(k),start,mid);
    update(nl,nr,val,rs(k),mid+1,end);
    tree[k] = tree[ls(k)] + tree[rs(k)];
}

最后就是查询函数:

inline ll query(ll nl, ll nr,ll k , ll start, ll end){
    lazy_push_down(k,start,end);
    ll leftsum = 0, rightsum = 0;
    if(nl <= start && nr >=end) return tree[k];
    // if(nl > end || nr < start) return 0;
    if(start == end) return tree[k];
    ll mid = (end + start) >> 1;
    if(nl <= mid)  leftsum = query(nl,nr,ls(k),start,mid);
    if(nr > mid)   rightsum = query(nl,nr,rs(k),mid+1,end);
    return leftsum + rightsum;
}

全部代码:

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

typedef  long long ll;

vector<ll> arr;
vector<ll> tree;
vector<ll> tag;

inline ll ls(int p){
    return p<<1;
}
inline ll rs(int p){
    return p<<1|1;
}

inline void buildtree(ll k, ll start, ll end){
    if(start == end){
        tree[k] = arr[start];
        return;
    };
    ll mid = (start + end) >> 1;
    buildtree(ls(k),start,mid);
    buildtree(rs(k),mid+1,end);
    tree[k] = tree[ls(k)] + tree[rs(k)];    /*回溯修改父节点*/
}

/*lazy标记*/
inline void lazy_push_down(ll k , ll start, ll end){
    if(tag[k] != 0){
        tree[k] += (end - start + 1)*tag[k];
        if(start != end){
            tag[ls(k)] += tag[k];
            tag[rs(k)] += tag[k];
        }
        tag[k] = 0;
    }
}

/*区间修改*/
inline void update(ll nl, ll nr, ll val, ll k, ll start, ll end){
    lazy_push_down(k,start,end);
    if(nl <= start && nr >= end){
        tree[k] += (end - start + 1)*val;
        if(start != end){
            tag[ls(k)] += val;
            tag[rs(k)] += val;
            lazy_push_down(k,start,end);
        }
        return;
    }
    if(nl > end || nr < start) return;
    ll mid = (end + start) >> 1;
    update(nl,nr,val,ls(k),start,mid);
    update(nl,nr,val,rs(k),mid+1,end);
    tree[k] = tree[ls(k)] + tree[rs(k)];
}

/*单点修改*/
inline void update(ll p,ll val, ll k, ll start, ll end){
    ll mid = (start + end) >> 1;
    if(start == end){
        if(p == start) {
            tree[k] = val;
            return;
        }
    }
    if(p <= mid) update(p,val,ls(k),start,mid);
    if(p > mid) update(p,val,rs(k),mid+1,end);
    tree[k] = tree[ls(k)] + tree[rs(k)];
}

inline ll query(ll nl, ll nr,ll k , ll start, ll end){
    lazy_push_down(k,start,end);
    ll leftsum = 0, rightsum = 0;
    if(nl <= start && nr >=end) return tree[k];
    // if(nl > end || nr < start) return 0;
    if(start == end) return tree[k];
    ll mid = (end + start) >> 1;
    if(nl <= mid)  leftsum = query(nl,nr,ls(k),start,mid);
    if(nr > mid)   rightsum = query(nl,nr,rs(k),mid+1,end);
    return leftsum + rightsum;
}

int main(){
    ll n ,m;
    // scanf("%lld%lld",&n,&m);
    cin >> n >> m;
    arr.resize(n+1,0), tree.resize(n<<2,0), tag.resize(n<<2,0);
    for(ll i = 1; i <= n ; ++i){
        cin >> arr[i];
    }
    buildtree(1,1,n);
    while(m--){
        ll f,a,b;
        // scanf("%d%d%d%d",&f,&a,&b,&c);
        cin >> f >> a >> b ;
        if(f == 1) {
            ll c;
            cin >> c;
            update(a,b,c,1,1,n);    /*区间修改*/
            // update(a,b,1,1,n);   /*索引修改*/
            }
        else
            cout << query(a,b,1,1,n) << endl;
       
    }
    return 0;
}

运行结果:

  • 9
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
C语言的线段树可以使用数组来实现,每个节点表示一个区间。以下是一个基本的线段树结构体及其相关函数的示例代码: ```c const int MAXN = 100005; // 区间长度的最大值 struct SegTreeNode { int l, r; // 区间左右端点 int sum; // 区间内元素的和(这里以求和为例) // 构造函数 SegTreeNode() : l(0), r(0), sum(0) {} // 复制构造函数 SegTreeNode(const SegTreeNode& o) : l(o.l), r(o.r), sum(o.sum) {} }; SegTreeNode segTree[MAXN << 2]; // 线段树数组,使用区间长度的4倍大小 // 建立线段树(输入数组为a,当前节点为i,区间为[l, r]) void build(int i, int l, int r, int a[]) { segTree[i].l = l; segTree[i].r = r; if (l == r) { segTree[i].sum = a[l]; return; } int mid = (l + r) >> 1; build(i << 1, l, mid, a); build(i << 1 | 1, mid + 1, r, a); segTree[i].sum = segTree[i << 1].sum + segTree[i << 1 | 1].sum; } // 区间查询(查询区间[qL, qR]的和,当前节点为i) int query(int i, int qL, int qR) { if (segTree[i].l >= qL && segTree[i].r <= qR) { return segTree[i].sum; } int mid = (segTree[i].l + segTree[i].r) >> 1; int res = 0; if (qL <= mid) { res += query(i << 1, qL, qR); } if (qR > mid) { res += query(i << 1 | 1, qL, qR); } return res; } // 区间修改(将区间[pL, pR]加上x,当前节点为i) void modify(int i, int pL, int pR, int x) { if (segTree[i].l >= pL && segTree[i].r <= pR) { segTree[i].sum += x; return; } int mid = (segTree[i].l + segTree[i].r) >> 1; if (pL <= mid) { modify(i << 1, pL, pR, x); } if (pR > mid) { modify(i << 1 | 1, pL, pR, x); } segTree[i].sum = segTree[i << 1].sum + segTree[i << 1 | 1].sum; } ``` 使用示例: ```c #include <stdio.h> int main() { int n = 10; int a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; // 建立线段树 build
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值