目录
附录
线段树从入门到进阶第1题
题目弹射器:【模板】树状数组 1 - 洛谷
题目
先来看题:
[题目描述]
如题,已知一个数列,你需要进行下面两种操作:
将某一个数加上 𝑥
求出某区间每一个数的和
接下来 𝑚 行每行包含 3 个整数,表示一个操作,具体如下:
1 x k
含义:将第 𝑥 个数加上 𝑘
2 x y
含义:输出区间 [𝑥,𝑦] 内每个数的和[输出格式]
输出包含若干行整数,即为所有操作 2 的结果。
[输入输出样例]
[输入样例 #1]
5 5 1 5 4 2 3 1 1 3 2 2 5 1 3 -1 1 4 2 2 1 4
[输出样例 #1]
14 16
[说明/提示]
[数据范围]
对于 30% 的数据,1≤𝑛≤8,1≤𝑚≤10;
对于 70% 的数据,1≤𝑛,𝑚≤10⁴;
对于 100% 的数据,1≤𝑛,𝑚≤5×10⁵。数据保证对于任意时刻,𝑎 的任意子区间(包括长度为 1 和 𝑛的子区间)和均在 [−231,231)范围内。
[样例说明]
故输出结果14、16
解法
1.树状数组
这是一道树状数组的模板题,对于lowbit、modify和query函数这里将不再赘述,不会的同学请移步这边:树状数组概览-CSDN博客。如果你会线段数且不想学习树状数组,建议你直接跳到线段树解法食用。
学会了树状数组的三个重要函数,这道题目就可以直接做出来了,下面是代码:
// P3374 【模板】树状数组 1
#include <bits/stdc++.h>
using namespace std;
const int maxn=5e5+5;
int n,m,num,ask,x,y,tree[maxn];
int lowbit(int id) {
return id&(-id);
}
void modify(int id,int k) {
while (id<=n) {
tree[id]+=k;
id+=lowbit(id);
}
return;
}
int query(int id) {
int ans=0;
while (id!=0) {
ans+=tree[id];
id-=lowbit(id);
}
return ans;
}
int main() {
scanf("%d %d",&n,&m);
for (int i=1;i<=n;i++) {
scanf("%d",&num);
modify(i,num);
}
for (int i=1;i<=m;i++) {
scanf("%d %d %d",&ask,&x,&y);
if (ask==1)
modify(x,y);
else
cout<<query(y)-query(x-1)<<"\n";
}
return 0;
}
2.线段树
(线段树这里的函数名可能不太规范,大家按自己喜好来就可以)
同样,如果你不会线段树,请移步这边:线段树概览-CSDN博客。
如果你已经阅读上面博客,了解线段树大致思路。接下来所要做的就是就是将代码里用...省略的部分补充完整。
I.结构体
很明显,结构体只需要添加一个记录区间和的sum变量即可:
struct Tree {
int l,r,sum;
}tree[maxm];
II.初始化线段树
当这一个节点的区间的l==r时,我们要将tree[id].sum设为当前区间所代表的数,利用num[]数组存储,则有tree[id].sum=num[tree[id].l]。如果不是,往下遍历后将当前区间和设为两个孩子节点区间和的和,即tree[id].sum=tree[id*2].sum+tree[id*2+1].sum。以下是代码:
void build(int l,int r,int id) { // 构建线段树
tree[id].l=l;
tree[id].r=r;
if (l==r) {
tree[id].sum=num[tree[id].l];
return;
}
int mid=(l+r)/2;
build(l,mid,id*2);
build(mid+1,r,id*2+1);
tree[id].sum=tree[id*2].sum+tree[id*2+1].sum;
return;
}
III.单点修改
很明显,每次查询只需给当前和加上需要加的数即可。以下是代码:
void push_down(int id,int k,int dis) { // 单点修改
tree[id].sum+=k;
if (tree[id].l==tree[id].r)
return;
int mid=(tree[id].l+tree[id].r)/2;
if (dis<=mid)
push_down(id*2,k,dis);
else
push_down(id*2+1,k,dis);
return;
}
IV.回答
回答也是比较明显的,判断如果在直接将tree[id].sum加入ans中。否则,判断是否有重叠片段,继续搜索。下面是代码:
void push_up(int id,int l,int r) { // 查询答案
if (tree[id].l>=l&&tree[id].r<=r) {
ans+=tree[id].sum;
return;
}
if (tree[id*2].r>=l)
push_up(id*2,l,r);
if (tree[id*2+1].l<=r)
push_up(id*2+1,l,r);
return;
}
V.代码
// 线段树
#include <bits/stdc++.h>
using namespace std;
const int maxx=5e5+5;
const int maxn=5e5*4+5;
int n,m,ans,num[maxx];
struct Tree {
int l,r,sum;
}tree[maxn];
void build(int l,int r,int id) { // 构建线段树
tree[id].l=l;
tree[id].r=r;
if (l==r) {
tree[id].sum=num[tree[id].l];
return;
}
int mid=(l+r)/2;
build(l,mid,id*2);
build(mid+1,r,id*2+1);
tree[id].sum=tree[id*2].sum+tree[id*2+1].sum;
return;
}
void push_down(int id,int k,int dis) { // 单点修改
tree[id].sum+=k;
if (tree[id].l==tree[id].r)
return;
int mid=(tree[id].l+tree[id].r)/2;
if (dis<=mid)
push_down(id*2,k,dis);
else
push_down(id*2+1,k,dis);
return;
}
void push_up(int id,int l,int r) { // 查询答案
if (tree[id].l>=l&&tree[id].r<=r) {
ans+=tree[id].sum;
return;
}
if (tree[id*2].r>=l)
push_up(id*2,l,r);
if (tree[id*2+1].l<=r)
push_up(id*2+1,l,r);
return;
}
int main() {
scanf("%d %d",&n,&m);
for (int i=1;i<=n;i++)
scanf("%d",&num[i]);
build(1,n,1);
for (int i=1;i<=m;i++) {
int ask,x,y;
scanf("%d %d %d",&ask,&x,&y);
if (ask==1)
push_down(1,y,x);
else {
ans=0;
push_up(1,x,y);
cout<<ans<<"\n";
}
}
return 0;
}
总结
纯模板题,第一次写可能会漏掉一些细节,学会模板就大概率可以轻松写出来了。
喜欢就点个赞吧!