树状数组、线段树

题目链接

P3374 【模板】树状数组 1
P3368 【模板】树状数组 2
P3372 【模板】线段树 1
P3373 【模板】线段树 2
P6242 【模板】线段树 3


P3374 【模板】树状数组 1

题目描述

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

  • 将某一个数加上 x

  • 求出某区间每一个数的和

输入格式

第一行包含两个正整数 n,m,分别表示该数列数字的个数和操作的总个数。

第二行包含 n 个用空格分隔的整数,其中第 i 个数字表示数列第 i 项的初始值。

接下来 m 行每行包含 3 个整数,表示一个操作,具体如下:

  • 1 x k 含义:将第 x 个数加上 k

  • 2 x y 含义:输出区间 [x,y] 内每个数的和

输出格式

输出包含若干行整数,即为所有操作 2 的结果。

输入

5 5 1 5 4 2 3 1 1 3 2 2 5 1 3 -1 1 4 2 2 1 4

输出

14 16

说明/提示

【数据范围】

对于 30% 的数据,1n81m10
对于 70% 的数据,1n,m104
对于 100% 的数据,1n,m5×105

  • 时间复杂度:O(logn)

代码

#include<bits/stdc++.h> using namespace std; using LL=long long; const int N=5e5+10; int n,m; int a[N],c[N],s[N]; LL ask(int x) { LL res=0; for(;x;x-=x&-x)res+=c[x]; return res; } void add(int x,int y) { for(;x<=n;x+=x&-x)c[x]+=y; } int main() { scanf("%d%d",&n,&m); int x; for(int i=1;i<=n;i++) { scanf("%d",&x); s[i]=s[i-1]+x; } while(m--) { int x,y,z; scanf("%d%d%d",&x,&y,&z); if(x==1)add(y,z); else printf("%lld\n",s[z]-s[y-1]+ask(z)-ask(y-1)); } return 0; }

P3368 【模板】树状数组 2

题目描述

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

  • 将某区间每一个数加上 x

  • 求出某一个数的值。

输入格式

第一行包含两个整数 NM,分别表示该数列数字的个数和操作的总个数。

第二行包含 N 个用空格分隔的整数,其中第 i 个数字表示数列第 i 项的初始值。

接下来 M 行每行包含 24个整数,表示一个操作,具体如下:

操作 1: 格式:1 x y k 含义:将区间 [x,y] 内每个数加上 k

操作 2: 格式:2 x 含义:输出第 x 个数的值。

输出格式

输出包含若干行整数,即为所有操作 2 的结果。

输入

5 5 1 5 4 2 3 1 2 4 2 2 3 1 1 5 -1 1 3 5 7 2 4

输出

6 10

说明/提示

数据规模与约定

对于 30% 的数据:N8,M10

对于 70% 的数据:N10000,M10000

对于 100% 的数据:1N,M5000001x,yn,保证任意时刻序列中任意元素的绝对值都不大于 230

  • 时间复杂度:O(mlogn)

代码

#include<bits/stdc++.h> using namespace std; using LL=long long; const int N=5e5+10; int n,m; int a[N],c[N]; LL ask(int x) { LL res=0; for(;x;x-=x&-x)res+=c[x]; return res; } void add(int x,int y) { for(;x<=n;x+=x&-x)c[x]+=y; } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%d",&a[i]); while(m--) { int op,x,y,k; scanf("%d%d",&op,&x); if(op==1) { scanf("%d%d",&y,&k); add(x,k); add(y+1,-k); } else printf("%lld\n",a[x]+ask(x)); } return 0; }

P3372 【模板】线段树 1

题目描述

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

  • 将某区间每一个数加上 k
  • 求出某区间每一个数的和。

输入格式

第一行包含两个整数 n,m,分别表示该数列数字的个数和操作的总个数。

第二行包含 n 个用空格分隔的整数,其中第 i 个数字表示数列第 i 项的初始值。

接下来 m 行每行包含 34 个整数,表示一个操作,具体如下:

  1. 1 x y k:将区间 [x,y] 内每个数加上 k
  2. 2 x y:输出区间 [x,y] 内每个数的和。

输出格式

输出包含若干行整数,即为所有操作 2 的结果。

输入

5 5 1 5 4 2 3 2 2 4 1 2 3 2 2 3 4 1 1 5 1 2 1 4

输出

11 8 20

说明/提示

对于 30% 的数据:n8m10
对于 70% 的数据:n103m104
对于 100% 的数据:1n,m105

保证任意时刻数列中任意元素的和在 [u263,263) 内。

树状数组

  • 解法
    我们不妨用树状数组维护前缀和,i=1xc[i] 就是 a[x] 增加的值,而序列 a 的前缀和 a[1x] 增加的值为:

i=1xj=1ic[j]i=1x(xi+1)×c[i](x+1)i=1xc[i]i=1xi×c[i]

  • 时间复杂度:O(mlogn)

代码

#include<bits/stdc++.h> using namespace std; const int N=1e5+10; using LL=long long; LL c[2][N],s[N]; int n,m; LL ask(int x,int k) { LL res=0; for(;x;x-=x&-x)res+=c[k][x]; return res; } void add(int x,int y,int k) { for(;x<=n;x+=x&-x)c[k][x]+=y; } int main() { scanf("%d%d",&n,&m); LL x; for(int i=1;i<=n;i++) { scanf("%lld",&x); s[i]=s[i-1]+x; } while(m--) { int op,x,y; LL k; scanf("%d%d%d",&op,&x,&y); if(op==1) { scanf("%lld",&k); add(x,k,0); add(y+1,-k,0); add(x,x*k,1); add(y+1,-(y+1)*k,1); } else { LL res=s[y]-s[x-1]; res+=(y+1)*ask(y,0)-ask(y,1)-(x*ask(x-1,0)-ask(x-1,1)); printf("%lld\n",res); } } return 0; }

线段树(懒标记)

  • 时间复杂度:O(mlogn)

代码

#include<bits/stdc++.h> using namespace std; const int N=1e5+10; using LL=long long; struct seg { int l,r; LL sum,add; #define l(x) tr[x].l #define r(x) tr[x].r #define sum(x) tr[x].sum #define add(x) tr[x].add }tr[N*4]; int n,m; int a[N]; void build(int p,int l,int r) { l(p)=l,r(p)=r; if(l==r)sum(p)=a[l]; else { int mid=l+r>>1; build(p*2,l,mid),build(p*2+1,mid+1,r); sum(p)=sum(p*2)+sum(p*2+1); } } void spread(int p) { if(add(p)) { sum(p*2)+=add(p)*(r(p*2)-l(p*2)+1); sum(p*2+1)+=add(p)*(r(p*2+1)-l(p*2+1)+1); add(p*2)+=add(p); add(p*2+1)+=add(p); add(p)=0; } } void change(int p,int l,int r,int d) { if(l<=l(p)&&r(p)<=r) { sum(p)+=1ll*d*(r(p)-l(p)+1); add(p)+=d; return ; } spread(p); int mid=l(p)+r(p)>>1; if(l<=mid)change(p*2,l,r,d); if(r>mid)change(p*2+1,l,r,d); sum(p)=sum(p*2)+sum(p*2+1); } LL ask(int p,int l,int r) { if(l<=l(p)&&r(p)<=r) return sum(p); spread(p); LL res=0; int mid=l(p)+r(p)>>1; if(l<=mid)res+=ask(p*2,l,r); if(r>mid)res+=ask(p*2+1,l,r); return res; } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%d",&a[i]); build(1,1,n); while(m--) { int op,x,y; LL k; scanf("%d%d%d",&op,&x,&y); if(op==1) { scanf("%lld",&k); change(1,x,y,k); } else printf("%lld\n",ask(1,x,y)); } return 0; }

P3373 【模板】线段树 2

题目描述

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

  • 将某区间每一个数乘上 x

  • 将某区间每一个数加上 x

  • 求出某区间每一个数的和

输入格式

第一行包含三个整数 n,m,p,分别表示该数列数字的个数、操作的总个数和模数。

第二行包含 n 个用空格分隔的整数,其中第 i 个数字表示数列第 i 项的初始值。

接下来 m 行每行包含若干个整数,表示一个操作,具体如下:

操作 1: 格式:1 x y k 含义:将区间 [x,y] 内每个数乘上 k

操作 2: 格式:2 x y k 含义:将区间 [x,y] 内每个数加上 k

操作 3: 格式:3 x y 含义:输出区间 [x,y] 内每个数的和对 p 取模所得的结果

输出格式

输出包含若干行整数,即为所有操作 3 的结果。

输入

5 5 38 1 5 4 2 3 2 1 4 1 3 2 5 1 2 4 2 2 3 5 5 3 1 4

输出

17 2

说明/提示

【数据范围】

对于 30% 的数据:n8m10
对于 70% 的数据:n103m104
对于 100% 的数据:n105m105

除样例外,p=571373

解题思路

本题的关键在于加法和乘法的顺序问题:

  1. 加法优先级低,不会对前面的操作有影响
  2. 乘法优先级高,对前面的操作有影响,有三种影响:sum、懒标记 muladd

所以,采取先乘后加的方式

  • 时间复杂度:O(mlogn)

代码

#include<bits/stdc++.h> using namespace std; const int N=1e5+10; using LL=long long; struct seg { int l,r; LL sum,add,mul; #define l(x) tr[x].l #define r(x) tr[x].r #define add(x) tr[x].add #define mul(x) tr[x].mul #define sum(x) tr[x].sum }tr[N*4]; int n,m,mod; int a[N]; void build(int p,int l,int r) { l(p)=l,r(p)=r; mul(p)=1; if(l==r) { sum(p)=a[l]%mod; return ; } int mid=l+r>>1; build(p*2,l,mid),build(p*2+1,mid+1,r); sum(p)=(sum(p*2)+sum(p*2+1))%mod; } void spread(int p) { sum(p*2)=(sum(p*2)*mul(p)+add(p)*(r(p*2)-l(p*2)+1))%mod; sum(p*2+1)=(sum(p*2+1)*mul(p)+add(p)*(r(p*2+1)-l(p*2+1)+1))%mod; add(p*2)=(add(p*2)*mul(p)+add(p))%mod; add(p*2+1)=(add(p*2+1)*mul(p)+add(p))%mod; mul(p*2)=mul(p*2)*mul(p)%mod; mul(p*2+1)=mul(p*2+1)*mul(p)%mod; add(p)=0; mul(p)=1; } void change1(int p,int l,int r,int d) { if(l<=l(p)&&r(p)<=r) { add(p)=add(p)*d%mod; mul(p)=mul(p)*d%mod; sum(p)=(sum(p)*d)%mod; return ; } spread(p); int mid=l(p)+r(p)>>1; if(l<=mid)change1(p*2,l,r,d); if(r>mid)change1(p*2+1,l,r,d); sum(p)=(sum(p*2)+sum(p*2+1))%mod; } void change2(int p,int l,int r,int d) { if(l<=l(p)&&r(p)<=r) { add(p)=(add(p)+d)%mod; sum(p)=(sum(p)+(r(p)-l(p)+1)*d)%mod; return ; } spread(p); int mid=l(p)+r(p)>>1; if(l<=mid)change2(p*2,l,r,d); if(r>mid)change2(p*2+1,l,r,d); sum(p)=(sum(p*2)+sum(p*2+1))%mod; } int ask(int p,int l,int r) { if(l<=l(p)&&r(p)<=r)return sum(p); spread(p); int res=0; int mid=l(p)+r(p)>>1; if(l<=mid)res+=ask(p*2,l,r)%mod; if(r>mid)res+=ask(p*2+1,l,r); return res%mod; } int main() { scanf("%d%d%d",&n,&m,&mod); for(int i=1;i<=n;i++)scanf("%d",&a[i]); build(1,1,n); while(m--) { int op,x,y,k; scanf("%d%d%d",&op,&x,&y); if(op==1) { scanf("%d",&k); change1(1,x,y,k); } else if(op==2) { scanf("%d",&k); change2(1,x,y,k); } else printf("%d\n",ask(1,x,y)); } return 0; }

P6242 【模板】线段树 3

题目描述

给出一个长度为 n 的数列 A,同时定义一个辅助数组 BB 开始与 A 完全相同。接下来进行了 m 次操作,操作有五种类型,按以下格式给出:

1 l r k:对于所有的 i[l,r]],将 Ai 加上 kk 可以为负数)。
2 l r v:对于所有的 i[l,r],将 Ai 变成 min(Ai,v)
3 l r:求 i=lrAi
4 l r:对于所有的 i[l,r],求 Ai 的最大值。
5 l r:对于所有的 i[l,r],求 Bi 的最大值。
在每一次操作后,我们都进行一次更新,让 Bimax(Bi,Ai)

输入格式

第一行包含两个正整数 n,m,分别表示数列 A 的长度和操作次数。

第二行包含 n 个整数 A1,A2,,An ,表示数列 A

接下来 m 行,每行行首有一个整数 op,表示操作类型;接下来两个或三个整数表示操作参数,格式见【题目描述】。

输出格式

对于 op{3,4,5} 的操作,输出一行包含一个整数,表示这个询问的答案。

输入

5 6 1 2 3 4 5 3 2 5 1 1 3 3 4 2 4 2 3 4 1 5 1 5 3 1 4

输出

14 6 6 11

解题思路

代码


__EOF__

本文作者acwing_zyy
本文链接https://www.cnblogs.com/zyyun/p/15321146.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   zyy2001  阅读(43)  评论(0编辑  收藏  举报
编辑推荐:
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示