[模板]线段树的建树、查询、单点更新、区间更新

本文详细介绍线段树的基本构造方法,包括建树、查询、单点更新及区间更新等操作,并提供具体的实现代码。

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


此线段树以求区间最小值为例,求区间和只需适当更改pushup()函数即可


建树

int arr[N];
int SetTree[N*4];
int pushup(int root)
{
    return min(SetTree[root<<1], SetTree[root<<1|1]);
}
void build(int root, int left, int right)
{
    if(left == right)
    {
        SetTree[root] = arr[left];
        return;
    }
    int mid = (left + right) >> 1;
    build(root<<1, left, mid);
    build(root<<1|1, mid+1, right);
    SetTree[root] = pushup(root);
}
查询

int query(int root, int left, int right, int L, int R)
{
    if(L > right || R < left)
        return 1<<29;
    if(L <= left && R >= right)
        return SetTree[root];
    int mid = (left + right) >> 1;
    int l = query(root<<1, left, mid, L, R);
    int r = query(root<<1|1, mid+1, right, L, R);
    return min(l, r);
}

单点更新

int pushup(int root)
{
    return min(SetTree[root<<1], SetTree[root<<1|1]);
}
void update(int root, int left, int right, int index, int value)
{
    if(left == right)
    {
        if(index == left)
            SetTree[root] = value;
        return;
    }
    int mid = (left + right) >> 1;
    if(index <= mid)
        update(root<<1, left, mid, index, value);
    else
        update(root<<1|1, mid+1, right, index, value);
    SetTree[root] = pushup(root);
}



区间更新(注意延迟标记这个概念)

struct{
    int val;
    int mark;
}SetTree[N*4];
int pushup(int root)
{
    return SetTree[root<<1].val + SetTree[root<<1|1].val;
}
//传递延迟标记给子节点
void pushdown(int root, int left, int mid, int right)
{
    if(SetTree[root].mark != 0)
    {
        //延迟标记传递
        SetTree[root<<1].mark = SetTree[root].mark;
        SetTree[root<<1|1].mark = SetTree[root].mark;
        //区间和也要变化
        SetTree[root<<1].val = SetTree[root].mark * (mid-left+1);
        SetTree[root<<1|1].val = SetTree[root].mark * (right-mid);
        //向下传递后,root的mark清空
        SetTree[root].mark = 0;
    }
}
//区间更新
void update(int root, int left, int right, int l, int r, int mark)
{
    if(l > right || r < left)
        return;
    if(l <= left && r >= right)
    {
        SetTree[root].mark = mark;
        SetTree[root].val = mark * (right-left+1);
        return;
    }
    int mid = (left + right) >> 1;
    pushdown(root, left, mid, right);
    update(root<<1, left, mid, l, r, mark);
    update(root<<1|1, mid+1, right, l, r, mark);
    SetTree[root].val = pushup(root);
}

或者

struct{
    int val;
    int mark;
    int l;//区间下界
    int r;//区间上界
}Tree[N*4];
int pushup(int root)
{
    return Tree[root<<1].val + Tree[root<<1|1].val;
}
//传递延迟标记给子节点
void pushdown(int root)
{
    if(Tree[root].mark != 0)
    {
        int mid = (Tree[root].l + Tree[root].r) >> 1;
        Tree[root<<1].mark = Tree[root].mark;
        Tree[root<<1|1].mark = Tree[root].mark;
        Tree[root<<1].val = Tree[root].mark * (mid-Tree[root].l+1);
        Tree[root<<1|1].val = Tree[root].mark * (Tree[root].r-mid);
        Tree[root].mark = 0;
    }
}
//区间更新
void update(int root, int l, int r, int value)
{
    if(l > Tree[root].r || r < Tree[root].l)
        return;
    if(l <= Tree[root].l && r >= Tree[root].r)
    {
        Tree[root].mark = value;
        Tree[root].val = value * (Tree[root].r-Tree[root].l+1);
        return;
    }
    pushdown(root);
    update(root<<1, l, r, value);
    update(root<<1|1, l , r, value);
    Tree[root].val = pushup(root);
}


<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、付费专栏及课程。

余额充值