对于线段树的研究
基本思想
线段树(Segment Tree)是一种特殊的二叉树,每个节点维护一个区间,可以用来处理区间更新、单点修改、单点查询、区间查询的数据结构。
建树(build)
建树使用分治的思想,将一个区间 拆成两份,分别为 与 ,分别处理,直到遇到叶子结点()为止,再上推数据(pushup)。完成建树。
上推数据(pushup)
线段树的每一个节点不止存储了区间信息,还存储了维护的值(maintain value)。在建树的过程中更新每个节点的数据。
单点查询(query)
单点查询使用二分的思想。
如果查询的点的下标是,那么先遍历节点 (根节点)。如果小于那么往左半区间搜索,否则往右半区间搜索。
区间查询(query)
如果需要查询空间被当前空间包含,那么直接返回该空间的值。
如果没有,将查询空间分成两份,把当前区间也分成两份,如果满足条件,就继续搜索,直到被包含。
单点修改(update)
先用单点查询的思想找到叶子节点,更改他,然后逐级上推即可。
区间修改(update)
区间修改使用了懒标记(lazy tag)的思想。
如果直接遍历线段树进行暴力修改,那么有很大的可能性会超时,因此我们可以使用懒标记标记这个节点,等到需要使用的时候再进行下推标记(pushdown)
下推标记(pushdown)
遇到了标记,直接将储存的标记向下推,更改维护值,取消标记即可。
模板
线段树的应用过于广泛,我们在后面的应用再进行展示。
时空复杂度分析
空间复杂度
线段树整体空间复杂度比较难算,但是一定不会超过。所以开数组的时候可以开n<<2
的数组。
另外,标记数组也需要开四倍。
除此之外,线段树一般不需要其他辅助空间。
时间复杂度
- 建树:时间复杂度:
- 其他操作:时间复杂度:。
应用
维护区间和的问题
例题:洛谷P3372 【模板】线段树 1
如题,已知一个数列,你需要进行下面两种操作:
- 将某区间每一个数加上 。
- 求出某区间每一个数的和。
涉及操作:区间修改、区间查询、建树(这个可以忽略)。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m;
#define ls (now<<1)
#define rs (now<<1|1)
#define mid (l+r>>1)
int tree[100001<<2],tag[100001<<2];
void pushup(int now){
tree[now]=tree[ls]+tree[rs];
}
void pushdown(int now,int l,int r){
if(tag[now]){
tag[ls]+=tag[now];
tag[rs]+=tag[now];
tree[ls]+=(mid-l+1)*tag[now];
tree[rs]+=(r-mid)*tag[now];
tag[now]=0;
}
}
void build(int now,int l,int r){
if(l==r){
cin>>tree[now];
return ;
}
build(ls,l,mid);
build(rs,mid+1,r);
pushup(now);
}
void add(int now,int l,int r,int x,int y,int k){
if(l>=x&&r<=y){
tree[now]+=(r-l+1)*k;
tag[now]+=k;
return ;
}
pushdown(now,l,r);
if(mid>=x) add(ls,l,mid,x,y,k);
if(mid<y) add(rs,mid+1,r,x,y,k);
pushup(now);
}
int ask(int now,int l,int r,int x,int y){
if(l>=x&&r<=y){
return tree[now];
}
pushdown(now,l,r);
int ret=0;
if(mid>=x) ret+=ask(ls,l,mid,x,y);
if(mid<y) ret+=ask(rs,mid+1,r,x,y);
return ret;
}
signed main(){
cin>>n>>m;
build(1,1,n);
while(m--){
int op,x,y,k;
cin>>op;
if(op==1){
cin>>x>>y>>k;
add(1,1,n,x,y,k);
}
else{
cin>>x>>y;
cout<<ask(1,1,n,x,y)<<'\n';
}
}
}
总结:维护区间和类问题的pushup
操作是。
两个tag
例题:洛谷P3373 【模板】线段树 2
#include <bits/stdc++.h>
using namespace std;
long long c[500010];
long long p;
struct sgt{
long long sum[2000010];
long long addv[2000010];
long long mulv[2000010];
void build(int o,int l,int r){
addv[o]=0;
mulv[o]=1;
if(l==r)sum[o]=c[l];
else{
int mid=(l+r)>>1;
int lson=o<<1;
int rson=lson|1;
build(lson,l,mid);
build(rson,mid+1,r);
sum[o]=(sum[lson]+sum[rson])%p;
}
}
void push_down(int o,int l,int r,int mid,int lson,int rson){
mulv[lson]=(mulv[lson]*mulv[o])%p;
mulv[rson]=(mulv[rson]*mulv[o])%p;
addv[lson]=(addv[lson]*mulv[o])%p;
addv[rson]=(addv[rson]*mulv[o])%p;
sum[lson]=(sum[lson]*mulv[o])%p;
sum[rson]=(sum[rson]*mulv[o])%p;
mulv[o]=1;
addv[lson]=(addv[lson]+addv[o])%p;
addv[rson]=(addv[rson]+addv[o])%p;
sum[lson]=(sum[lson]+(mid-l+1)*addv[o])%p;
sum[rson]=(sum[rson]+(r-mid)*addv[o])%p;
addv[o]=0;
}
void addall(int o,int l,int r,int a,int b,int x){
if(l>=a && r<=b){
addv[o]=(addv[o]+x)%p;
sum[o]=(sum[o]+(r-l+1)*x)%p;
return;
}
else{
int mid=(l+r)>>1;
int lson=o<<1;
int rson=lson|1;
if(mulv[o]!=1 || addv[o])push_down(o,l,r,mid,lson,rson);
if(a<=mid)addall(lson,l,mid,a,b,x);
if(b>mid)addall(rson,mid+1,r,a,b,x);
sum[o]=(sum[lson]+sum[rson])%p;
}
}
void mulall(int o,int l,int r,int a,int b,int x){
if(l>=a && r<=b){
mulv[o]=(mulv[o]*x)%p;
addv[o]=(addv[o]*x)%p;
sum[o]=(sum[o]*x)%p;
return;
}
else{
int mid=(l+r)>>1;
int lson=o<<1;
int rson=lson|1;
if(mulv[o]!=1 || addv[o])push_down(o,l,r,mid,lson,rson);
if(a<=mid)mulall(lson,l,mid,a,b,x);
if(b>mid)mulall(rson,mid+1,r,a,b,x);
sum[o]=(sum[lson]+sum[rson])%p;
}
}
long long query(int o,int l,int r,int a,int b){
if(l>=a && r<=b)return sum[o]%p;
else{
int mid=(l+r)>>1;
int lson=o<<1;
int rson=lson|1;
long long ans=0;
if(mulv[o]!=1 || addv[o])push_down(o,l,r,mid,lson,rson);
if(a<=mid)ans+=query(lson,l,mid,a,b);
if(b>mid)ans+=query(rson,mid+1,r,a,b);
return ans%p;
}
}
} tree;
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
int n,m;
cin>>n>>m>>p;
for(int i=1;i<=n;i++){
cin>>c[i];
}
tree.build(1,1,n);
for(int i=1;i<=m;i++){
int f;
long long x,y,k;
cin>>f;
if(f==1){
cin>>x>>y>>k;
tree.mulall(1,1,n,x,y,k);
}
else if(f==2){
cin>>x>>y>>k;
tree.addall(1,1,n,x,y,k);
}
else{
cin>>x>>y;
cout<<tree.query(1,1,n,x,y)<<endl;
}
}
return 0;
}
复杂的维护问题
例题:洛谷P4041 [AHOI2014/JSOI2014]奇怪的计算器
#include <bits/stdc++.h>
#define int long long
#define soi sort
using namespace std;
const signed N = 100005;
const signed T = 4 * N + 5;
signed n, mn, mx, q;
int minv[T], maxv[T], tagadd[T], tangmul[T], tat[T], tagset[T];
struct Option {
char s;
int a;
}op[N];
struct Number {
int id, val, ans;
}num[N];
bool cmp1(Number a, Number b) {
return a.val < b.val;
}
bool cmp2(Number a, Number b) {
return a.id < b.id;
}
void build(signed i, signed l, signed r) {
if (l == r) {
minv[i] = num[l].val;
maxv[i] = num[l].val;
return ;
}
tangmul[i] = 1;
signed mid = l + r >> 1;
build(i << 1, l, mid);
build(i << 1 | 1, mid + 1, r);
minv[i] = min(minv[i << 1], minv[i << 1 | 1]);
maxv[i] = max(maxv[i << 1], maxv[i << 1 | 1]);
}
void pushadd(signed i, signed l, signed r, int x) {
minv[i] += x;
maxv[i] += x;
tagadd[i] += x;
}
void pushmul(signed i, signed l, signed r, int x) {
minv[i] *= x;
maxv[i] *= x;
tangmul[i] *= x;
tagadd[i] *= x;
tat[i] *= x;
}
void pushad(signed i, signed l, signed r, int x) {
minv[i] += num[l].val * x;
maxv[i] += num[r].val * x;
tat[i]+=x;
}
void pushset(signed i, signed l, signed r, int x) {
minv[i] = maxv[i] = x;
tagset[i] = x;
tagadd[i] = 0;
tangmul[i] = 1;
tat[i] = 0;
}
void pushdown(signed i, signed l, signed r) {
signed mid = l + r >> 1;
if (tagset[i] != 0) {
pushset(i << 1, l, mid, tagset[i]);
pushset(i << 1 | 1, mid + 1, r, tagset[i]);
tagset[i] = 0;
}
if (tangmul[i] != 1) {
pushmul(i << 1, l, mid, tangmul[i]);
pushmul(i << 1 | 1, mid + 1, r, tangmul[i]);
tangmul[i] = 1;
}
if (tagadd[i] != 0) {
pushadd(i << 1, l, mid, tagadd[i]);
pushadd(i << 1 | 1, mid + 1, r, tagadd[i]);
tagadd[i] = 0;
}
if (tat[i] != 0) {
pushad(i << 1, l, mid, tat[i]);
pushad(i << 1 | 1, mid + 1, r, tat[i]);
tat[i] = 0;
}
}
void setmin(signed i, signed l, signed r) {
if (maxv[i] < mn) {
pushset(i, l, r, mn);
return ;
}
if (l == r)
return ;
pushdown(i, l, r);
signed mid = l + r >> 1;
setmin(i << 1, l, mid);
if (minv[i << 1 | 1] < mn)
setmin(i << 1 | 1, mid + 1, r);
minv[i] = min(minv[i << 1], minv[i << 1 | 1]);
maxv[i] = max(maxv[i << 1], maxv[i << 1 | 1]);
}
void setmax(signed i, signed l, signed r) {
if (minv[i] > mx) {
pushset(i, l, r, mx);
return ;
}
if (l == r)
return ;
pushdown(i, l, r);
signed mid = l + r >> 1;
setmax(i << 1 | 1, mid + 1, r);
if (maxv[i << 1] > mx)
setmax(i << 1, l, mid);
minv[i] = min(minv[i << 1], minv[i << 1 | 1]);
maxv[i] = max(maxv[i << 1], maxv[i << 1 | 1]);
}
void getAnswer(signed i, signed l, signed r) {
if (l == r) {
num[l].ans = minv[i];
return ;
}
pushdown(i, l, r);
signed mid = l + r >> 1;
getAnswer(i << 1, l, mid);
getAnswer(i << 1 | 1, mid + 1, r);
}
signed main() {
scanf("%d%d%d", &n, &mn, &mx);
for (signed i = 1; i <= n; i++)
scanf("%s%lld", &op[i].s, &op[i].a);
scanf("%d", &q);
for (signed i = 1; i <= q; i++) {
scanf("%lld", &num[i].val);
num[i].id = i;
}
soi(num + 1, num + q + 1, cmp1);
build(1, 1, q);
for (signed i = 1; i <= n; i++) {
if (op[i].s == '+')
pushadd(1, 1, q, op[i].a); else if (op[i].s == '-')
pushadd(1, 1, q, -op[i].a); else if (op[i].s == '*')
pushmul(1, 1, q, op[i].a); else if (op[i].s == '@')
pushad(1, 1, q, op[i].a);
setmin(1, 1, q);
setmax(1, 1, q);
}
getAnswer(1, 1, q);
soi(num + 1, num + q + 1, cmp2);
for (signed i = 1; i <= q; i++){
cout << num[i].ans << endl;
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现