线段树(Segment Tree)是一种用于解决区间查询问题的数据结构。它将一个线性的区间划分成多个较小的子区间,并将每个子区间的信息进行存储和更新。通常情况下,线段树用于解决以下两类问题:
1. 区间查询问题:给定一个区间,需要查询该区间的某个特定信息,如求和、最大值、最小值等。
2. 区间更新问题:给定一个区间,需要对该区间的某个特定信息进行更新操作,如修改某个元素的值、增加某个元素的值等。
线段树的时间复杂度为 O(logN),其中 N 表示区间的长度。由于线段树的构建和查询操作都需要递归地访问每个节点,所以时间复杂度与树的高度有关,而树的高度为 O(logN)。
线段树(Segment Tree)是一种用于解决区间查询的数据结构。它可以高效地支持区间的修改和查询操作。
线段树的建立,修改,查询,更新操作可以分为如下几个步骤:
1. 建立:线段树是一棵完全二叉树,每个节点代表一个区间。在建立线段树时,首先需要定义好线段树的结构。通常将线段树的结构定义为一个数组,数组的长度是原始数组长度的四倍左右,这样可以确保线段树的高度不会超过4 * log(n),其中n是原始数组的长度。然后使用递归的方式,从根节点开始,不断将区间划分成两个子区间,直到区间只包含一个元素。在递归的过程中,可以根据实际需求来决定线段树节点所存储的信息,例如区间的最大值、最小值、和、平均值等。
2. 修改:修改操作用于更新线段树中某个节点代表的区间的值。首先需要找到需要修改的节点,如果当前节点代表的区间正好等于要修改的区间,则直接更新节点的值。否则,根据当前节点代表的区间和要修改的区间的关系,将修改操作转移到当前节点的左子节点或右子节点。
3. 查询:查询操作用于获取线段树中某个区间的信息。首先需要找到代表该区间的节点,如果当前节点代表的区间正好等于要查询的区间,则直接返回节点的值。否则,根据当前节点代表的区间和要查询的区间的关系,将查询操作转移到当前节点的左子节点或右子节点,并将子节点返回的信息合并起来。
4. 更新:更新操作是修改操作的一种特殊情况。当需要对线段树中某个区间的所有节点进行修改时,可以使用更新操作实现。首先需要找到代表该区间的节点,然后递归地将修改操作应用到当前节点的左子节点和右子节点,并更新当前节点的值。
下面就是实现线段树的 C++ 代码:
#include <bits/stdc++.h>
#define ll long long
#define endl "\n"
#define lc p << 1 // 左孩子的下标;
#define rc p << 1 | 1 // 右孩子下标;
#define KUI ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
using namespace std;
const int con = 1e5;
const int mod = 998244353;
int n;
int w[con];
struct jgt
{
int l, r, sum, add;
} tr[con * 4];
void build(int p, int l, int r)
{
tr[p].l = l; // 该节点p的左边界;
tr[p].r = r; // 该节点p的右边界;
if (l == r)
{
tr[p].sum = w[l];
return; // l==r,说明是叶子节点就赋值并返回;
}
int m = l + r >> 1; // 不是叶子节点就裂开;
build(lc, l, m); // 建左孩子,build(左孩子树节点,左孩子左边界,左孩子右边界);
build(rc, m + 1, r); // 建右孩子,build(右孩子树节点,右孩子左边界,右孩子右边界);
pushup(p); // 孩子节点建完就更新该节点sum值;
// 在build(lc, l, m);里面,lc是树的节点,l,r是数组的下标范围;
}
void pushup(int p)
{
tr[p].sum = tr[lc].sum + tr[rc].sum;
}
void pushdown(int p)
{
if (tr[p].add)
{
tr[lc].sum += tr[p].add * (tr[lc].r - tr[lc].l + 1);
tr[rc].sum += tr[p].add * (tr[rc].r - tr[rc].l + 1);
tr[lc].add += tr[p].add;
tr[rc].add += tr[p].add;
}
}
void update(int p, int x, int y, int k)
{
if (x <= tr[p].l && tr[p].r <= y)
{
// 当更新的范围包含该节点时,直接返回该节点的值,更新懒标记,不再向下更新,提高效率;
tr[p].sum += (tr[p].r - tr[p].l + 1) * k;
tr[p].add += k;
return;
}
int m = tr[p].l + tr[p].r >> 1; // m指的是左右节点的中间值;
if (tr[p].add)
{
pushdown(p);
// 当需要继续向下更新时,如果发现该节点存在懒标记,则更新孩子节点;
}
if (x <= m)
{
update(lc, x, y, k);
}
if (y > m)
{
update(rc, x, y, k);
}
pushup(p);
}
int query(int p, int x, int y)
{
if (x <= tr[p].l && tr[p].r <= y)
{
return tr[p].sum;
}
int m = tr[p].l + tr[p].r >> 1;
if (tr[p].add)
{
// 当需要继续向下查询时,如果发现该节点存在懒标记,则更新孩子节点;
pushdown(p);
}
int sum = 0;
if (x <= m)
{
sum += query(lc, x, y);
}
if (y > m)
{
sum += query(rc, x, y);
}
return sum;
}
void take()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> w[i];
}
build(1, 1, n); // 从根节点1开始建树,build(根节点,左边界,右边界);
query(1, 1, n); // 查询, query(根节点,查询左边界,查询右边界);
}
int main()
{
KUI;
int t1 = 1;
cin >> t1;
while (t1--)
{
take();
}
return 0;
}