(模板)线段树 (区间更新,区间求和)

本文介绍了一种使用线段树实现区间加法和区间求和的方法,具体包括线段树的构建、区间加法更新及区间求和查询等核心算法。适用于处理大量区间操作的问题。

已知一个数列,你需要进行下面两种操作:

1.将某区间每一个数加上x

2.求出某区间每一个数的和(参见洛谷P3372)
后面query和update的部分一定要小心begin,end和left,right不要弄混了。
可以认为left和right在整个程序中都是在代表一个范围

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn=100010;
int array1[maxn];
struct node1{
    ll sum,addmark;
}Node[maxn<<2];
void pushup(int node)
{
    Node[node].sum=Node[node<<1].sum+Node[node<<1|1].sum;
}
void buildtree(int node,int left,int right)
{
    Node[node].addmark=0;
    if(left==right){
        Node[node].sum=array1[left];return;
    }
    int m=(left+right)>>1;
    buildtree(node<<1,left,m);
    buildtree(node<<1|1,m+1,right);
    pushup(node);
}
void pushdown(int node,int left,int right)
{
    if(Node[node].addmark){
        int m=(left+right)>>1;
        Node[node<<1].addmark+=Node[node].addmark;
        Node[node<<1|1].addmark+=Node[node].addmark;
        Node[node<<1].sum+=(m-left+1)*Node[node].addmark;
        Node[node<<1|1].sum+=(right-m)*Node[node].addmark;
        Node[node].addmark=0;
    }
}
ll query(int node,int begin,int end,int left,int right)
{
    if(left<=begin&&right>=end)
        return Node[node].sum;
    pushdown(node,begin,end);
    int m=(begin+end)>>1;//小心!!
    ll ans=0;
    if(left<=m)
        ans+=query(node<<1,begin,m,left,right);
    if(right>m)
        ans+=query(node<<1|1,m+1,end,left,right);
    return ans;
}
void update(int node,int add,int begin,int end,int left,int right)
{
    if(begin>=left&&end<=right){
        Node[node].sum+=(end-begin+1)*add;
        Node[node].addmark+=add;return;
    }
    int m=(begin+end)>>1;//小心!!
    pushdown(node,begin,end);
    if(left<=m)
        update(node<<1,add,begin,m,left,right);
    if(right>m)
        update(node<<1|1,add,m+1,end,left,right);
    pushup(node);
}
int main()
{
    int n,m,i,j,k;
    cin>>n>>m;
    for(i=1;i<=n;i++)
        scanf("%d",&array1[i]);
    buildtree(1,1,n);
    for(i=1;i<=m;i++){
        int a,b,c,d;
        scanf("%d",&a);
        if(a==1){
            scanf("%d%d%d",&b,&c,&d);
            update(1,d,1,n,b,c);
        }
        else{
            scanf("%d%d",&b,&c);
            printf("%lld\n",query(1,1,n,b,c));
        }
    }

    return 0;
}
<think>嗯,用户需要一个C++实现的线段树模板,支持区间修改和区间查询。我得先回忆一下线段树的基本结构。线段树通常用结构体或类来实现,每个节点包含区间范围、左右子节点指针或数组索引,以及需要维护的数据,比如区间和、最大值等。还有,区间修改需要用到懒标记(lazy tag),这样能延迟更新,提高效率。 用户提到的引用[2]和[3]中提到,线段树区间修改和查询需要处理lazy tag。比如区间加和或者异或操作,这里用户可能需要的是通用的区间修改,比如加法操作。所以模板应该包含区间加法的懒标记处理。 首先,线段树的构建。通常用递归方法,当区间长度为1时,直接赋值;否则分割成左右子区间,递归构建左右子树。然后合并信息,比如区间和是左右子树的和之和。 然后是下推懒标记(push_down函数)。当当前节点有未处理的修改时,需要将标记传递给子节点,并更新子节点的值和懒标记。这里要注意,如果有多个懒标记类型(比如加法和乘法),处理顺序会有影响,但用户可能只需要加法,所以暂时处理加法的情况。 接下来是区间更新(update函数)。如果当前节点的区间完全在目标区间内,就更新当前节点的值并打上懒标记。否则,需要先下推现有的懒标记,然后根据左右子节点的情况递归更新区间查询(query函数)也类似,如果当前节点区间完全在查询区间内,直接返回值;否则下推懒标记后,分别查询左右子节点并合并结果。 引用[3]提到C++用类实现线段树模板,支持多种操作,比如区间求和、最大值,以及区间修改。所以代码结构应该是一个类,包含构建、更新、查询的方法。需要模板类吗?可能用户希望数据类型是灵活的,比如int或long long,所以模板参数可能有用。 用户可能需要完整的代码示例,所以需要提供一个具体的实现。比如,使用类成员变量存储左右边界、左右子节点索引、值、懒标记等。或者,使用数组来存储线段树的节点,这样可能更高效,但动态分配可能麻烦。另一种方法是使用结构体,每个节点包含必要的信息。 考虑到代码的简洁性,或许用数组存储线段树节点会更方便,但动态扩展可能有问题。或者,使用vector预先分配足够的空间,比如4倍原始数据大小,这是常见的做法。 例如,数据存储在一个vector中,每个节点包含l, r, sum, add标记。在建树时递归初始化这些值。update和query的时候处理区间覆盖和懒标记的下推。 现在,我需要检查代码是否符合用户的需求。用户要求区间修改和区间查询,所以代码中的update函数应该处理区间加法,query函数返回区间和。同时,要处理懒标记的正确下推。 可能还需要测试用例,比如构造一个数组,进行区间加法和查询,验证结果是否正确。但用户可能只需要模板代码,测试用例可以省略。 另外,引用[3]中提到,如果只需要区间和、加法、单点修改,树状数组可能更高效,但用户明确需要线段树模板,所以不用考虑树状数组。 总结:代码结构应该是类或结构体,包含建树、更新、查询、下推标记等方法。使用数组或vector存储节点,每个节点有区间范围、sum值、add标记。区间修改使用加法,支持懒标记的延迟更新。 现在,编写具体的代码。例如,使用类SegmentTree,构造函数接受数据数组。内部节点用结构体或数组存储。由于C++中动态分配子节点可能复杂,用数组索引的方式可能更简单,预先分配足够大的空间。 或者,采用堆式存储,即用数组表示二叉树,左子节点为2*i+1,右子节点为2*i+2。但这样空间可能浪费。另一种方法是动态创建节点,使用指针,但可能更复杂。 综合考虑,可能更简单的是预先分配一个足够大的数组,比如4*n的大小,其中n是原始数据的大小。每个节点包含l, r, sum, add。例如: struct Node { int l, r; int sum; int add; } tr[N * 4]; 然后用递归函数建树。update和query时处理区间分割和懒标记。 但用户可能需要一个完整的类实现,所以将线段树封装成类,内部使用数组存储节点,或者用vector。 例如: class SegmentTree { private: struct Node { int l, r; int sum; int add; }; vector<Node> tr; vector<int> nums; void push_up(int u) { tr[u].sum = tr[u<<1].sum + tr[u<<1|1].sum; } void push_down(int u) { if (tr[u].add) { int add = tr[u].add; tr[u<<1].sum += add * (tr[u<<1].r - tr[u<<1].l + 1); tr[u<<1|1].sum += add * (tr[u<<1|1].r - tr[u<<1|1].l + 1); tr[u<<1].add += add; tr[u<<1|1].add += add; tr[u].add = 0; } } void build(int u, int l, int r) { tr[u] = {l, r, 0, 0}; if (l == r) { tr[u].sum = nums[l]; return; } int mid = (l + r) >> 1; build(u<<1, l, mid); build(u<<1|1, mid+1, r); push_up(u); } void update(int u, int l, int r, int add) { if (tr[u].l >= l && tr[u].r <= r) { tr[u].sum += add * (tr[u].r - tr[u].l + 1); tr[u].add += add; return; } push_down(u); int mid = (tr[u].l + tr[u].r) >> 1; if (l <= mid) update(u<<1, l, r, add); if (r > mid) update(u<<1|1, l, r, add); push_up(u); } int query(int u, int l, int r) { if (tr[u].l >= l && tr[u].r <= r) { return tr[u].sum; } push_down(u); int mid = (tr[u].l + tr[u].r) >> 1; int sum = 0; if (l <= mid) sum += query(u<<1, l, r); if (r > mid) sum += query(u<<1|1, l, r); return sum; } public: SegmentTree(vector<int>& nums) : nums(nums) { int n = nums.size(); tr.resize(n * 4); build(1, 0, n - 1); } void update_range(int l, int r, int add) { update(1, l, r, add); } int query_range(int l, int r) { return query(1, l, r); } }; 不过,这里可能存在一些问题。比如,数组索引是否从1开始?在build函数中,u<<1和u<<1|1需要确保不会越界。此外,构造函数中的tr.resize(n*4)可能不够,需要确保足够的空间。或者,采用动态分配的方式,但可能比较复杂。 另外,在构造函数中,调用build(1, 0, n-1),假设根节点是1,左子节点是2,右是3,依此类推。这样,如果n很大,n*4是否足够?例如,当n是1e5时,tr的大小是4e5,这通常是足够的。 但需要确认线段树的数组大小是否正确。一般来说,线段树的大小是4*n,足够存储所有节点。因此,这种实现方式是可行的。 这样,用户可以使用这个类来初始化线段树,然后调用update_range和query_range进行区间修改和查询。 不过,用户可能需要更详细的注释或者说明,比如每个函数的作用,以及如何调用。因此,在代码中添加注释是必要的。 最后,根据用户提供的引用,尤其是引用[2]和[3],需要确保代码符合区间修改和查询的标准实现,使用懒标记,并正确处理各种情况。例如,push_down函数的正确性,以及在update和query时递归调用的条件是否正确。 综上所述,这样的代码应该满足用户的需求,提供一个C++实现的线段树模板,支持区间加法的修改和区间和的查询。</think>以下是一个支持区间加法修改与区间求和线段树C++模板代码实现,基于懒标记(lazy tag)技术: ```cpp #include <vector> using namespace std; class SegmentTree { private: struct Node { int l, r; // 区间端点 int sum; // 区间和 int add; // 懒标记(区间增量) }; vector<Node> tr; // 线段树数组 vector<int> nums; // 原始数据 // 更新父节点 void push_up(int u) { tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum; } // 下传懒标记 void push_down(int u) { if (tr[u].add) { int mid = (tr[u].l + tr[u].r) >> 1; // 更新左子树 tr[u << 1].sum += tr[u].add * (mid - tr[u].l + 1); tr[u << 1].add += tr[u].add; // 更新右子树 tr[u << 1 | 1].sum += tr[u].add * (tr[u].r - mid); tr[u << 1 | 1].add += tr[u].add; tr[u].add = 0; // 清除标记 } } // 建树 void build(int u, int l, int r) { tr[u] = {l, r, 0, 0}; if (l == r) { tr[u].sum = nums[l]; return; } int mid = (l + r) >> 1; build(u << 1, l, mid); build(u << 1 | 1, mid + 1, r); push_up(u); } // 区间修改 void update(int u, int l, int r, int add) { if (tr[u].l >= l && tr[u].r <= r) { tr[u].sum += add * (tr[u].r - tr[u].l + 1); tr[u].add += add; return; } push_down(u); int mid = (tr[u].l + tr[u].r) >> 1; if (l <= mid) update(u << 1, l, r, add); if (r > mid) update(u << 1 | 1, l, r, add); push_up(u); } // 区间查询 int query(int u, int l, int r) { if (tr[u].l >= l && tr[u].r <= r) return tr[u].sum; push_down(u); int mid = (tr[u].l + tr[u].r) >> 1; int sum = 0; if (l <= mid) sum += query(u << 1, l, r); if (r > mid) sum += query(u << 1 | 1, l, r); return sum; } public: SegmentTree(vector<int>& arr) : nums(arr) { int n = nums.size(); tr.resize(n * 4); // 预分配4倍空间 build(1, 0, n - 1); // 根节点从索引1开始 } // 对外接口:区间加法 void range_add(int l, int r, int val) { update(1, l, r, val); } // 对外接口:区间求和 int range_query(int l, int r) { return query(1, l, r); } }; ``` ### 使用示例 ```cpp int main() { vector<int> arr = {1,3,5,7,9}; SegmentTree st(arr); st.range_add(1, 3, 2); // 区间[1,3]每个元素+2 cout << st.range_query(0, 4); // 查询整个区间和 return 0; } ``` ### 代码特点 1. **时间复杂度**:建树$O(n)$,查询/修改操作$O(\log n)$ 2. **空间复杂度**:$O(4n)$预分配内存 3. **懒标记机制**:通过`push_down`实现延迟更新,确保时间复杂度最优[^2] 4. **区间操作**:支持任意区间的加法修改和求和查询 对于只需要单点修改的场景,可以通过调用`range_add(pos, pos, val)`实现。若需要支持其他区间操作(如区间乘、区间最值),需要调整节点存储的信息和懒标记处理逻辑[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值