我是小白,你有的困惑我也有!
😊😊 😊😊
不求点赞,只求耐心看完,指出您的疑惑和写的不好的地方,谢谢您。本人会及时更正感谢。希望看完后能帮助您理解算法的本质
😊😊 😊😊
题目:
给定长度为 N 的数列 A,然后输入 M 行操作指令。
第一类指令形如 C l r d,表示把数列中第 l∼r 个数都加 d 。
第二类指令形如 Q x,表示询问数列中第 x 个数的值。
输入格式
第一行包含两个整数 N 和 M。
第二行包含 N 个整数 A[i]。接下来 M 行表示 M 条指令,每条指令的格式如题目描述所示。
输出格式
对于每个询问,输出一个整数表示答案。
每个答案占一行。
数据范围
1≤N,M≤105,
|d|≤10000,
|A[i]|≤109
树状数组:
思路:
- 问题一:为什么要开long long 类型呢?
因为本题最坏情况下:10w个数据,并且每个元素的最大值为1e9,那么10w*1e9 = 1e14;爆int了! - 为什么插入的元素不是 add(i, a[i] ) 而是 add(i, a[i] - a[i-1]) 呢?
因为本题并没有求所谓的区间和前缀和,而是点查询和区间更新,对于点查询,如果我们直接插入a[i]而不是a[i] - a[i-1]的话,则查询方式为:sum(x) -sum(x-1);两次(logn)的查询。但是采用 a[i] - a[i-1] 的话,则不一样了,学过差分的都知道 差分数组可以由原数组预处理出来,当你求原数组的第 i 项的时候,只需要让差分数组中的前 i 相加求和即为原数组的第 i 项的数值!所以查询第 x 项,我们直接一个 sum(x)即可!让前 x 项相加求和! - 为什么是更新区间是:add (l, d) ;add (r+1, -d)呢?
1.第一个操作区间更新:即区间里面的元素均加上某个数。这一点可以采用差分数组进行维护。能够保持区间内的元素加上某个数,区间外的元素不修改!
b 是 a的差分数组:
b[i] = a[i] - a[i-1];
区间 [l, r] 增加 3;
b[l] += 3, b[r+1] -= 3;
2.第二个操作点查询:对于差分数组而言,要是想查询原数组中的某个元素,则直接差分数组求对应的前缀和即可!
总结:对于差分数组求前缀和,我们会想到树状数组和普通的求前缀和方法。对于修改区间里面的元素,修改后又要查询原数组的某个数 == 查询差分数组的区间和,即需要动态地维护区间和。所以我们应该采用树状数组进行维护差分数组。
其实本题用线段树的话,会更合适。
各位同学有不懂的问题记得评论,我一定回复,并且添加到此处!
代码:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10;
int n, m;
int a[N];
long long c[N];
int lowbit(int x)
{
return (-x) & x;
}
void add (int pos, int w)
{
for (int i=pos; i <= n; i += lowbit(i))
c[i] += w;
return ;
}
long long sum (int pos)
{
int s=0;
for (int i=pos; i>0; i-=lowbit(i))
s += c[i];
return s;
}
int main()
{
cin >> n >> m;
for (int i=1; i <= n; i ++)
{
cin >> a[i];
add (i, a[i]-a[i-1]);
}
while (m --)
{
char op;
cin >> op;
if (op == 'C'){
int l, r, d;
cin >> l >> r >> d;
add (l, d);
add (r+1, -d); //是将r后面的-d,所以不能包含r!
}
else{
int x;
cin >> x;
printf("%d\n", sum(x));
}
}
return 0;
}
线段树:
思路:
- 建树:将区间划分成完全二叉树的形式。通过递归二分区间,直到区间里面只有一个元素,即叶子节点的时候,才开始
回溯更新
。其余时候,就不断地二分区间端点,但记得把区间的左右端点存在树叶节点里面!
struct Node{
int l, r;
LL sum, lazy;
}tr[N]; //树中的节点!
void build(int u, int l, int r) //树节点编号,当前这个节点的左右端点!
{
tr[u] = {l, r};
if(l == r){
tr[u].sum += w[r]; //加上叶子节点权值。
return ;
}
int mid = (l + r) >> 1;
build (u<<1, l, mid);
build (u<<1|1, mid+1, r);
push_up(u); //回溯更新,下面一点会讲到!
}
- 回溯更新:第一点中的回溯更新打了标记,这里的回溯更新也就是 p u s h u p ( ) pushup() pushup() 函数,即递归到叶子节点后,开始向上回溯,利用子节点的信息更新父节点的信息。
void pushup(int u)//当执行到这里的时候,已经回溯了,表明此时的u为父节点。
{
tr[u].sum += tr[u<<1].sum + tr[u<<1|1].sum;
}
- 单点更新:由于是单点,所以该点的编号,必然是线段树中的叶子节点,既然是叶子节点,则左右端点相等,此时更新端点值 == 单点修改,但是更新后记得回溯更新 p u s h u p pushup pushup!另外注意,这里还涉及到了懒标记的查询迭代!
- 注意本题是区间更新哦,这里写单点更新是为了类比下:区间更新当你所要更新的区间范围覆盖了整个节点的左右端点的话,说明以该节点为根节点的子树都要进行区间更新。但是递归下去太繁琐了,所以在最上面那层打个标记,等下次路过的时候,并且还要往下访问的时候,再把懒标记带下去:
懒标记向下迭代:
void pushdown(int u)
{
tr[u<<1].lazy += tr[u].lazy;
tr[u<<1].sum += (tr[u<<1].r - tr[u<<1].l + 1) * tr[u].lazy;
tr[u<<1|1].lazy += tr[u].lazy;
tr[u<<1|1].sum += (tr[u<<1|1].r - tr[u<<1|1].r + 1) * tr[u].lazy;
tr[u].lazy = 0;
}
void add (int u, int l, int r, int val)
{
if (tr[u].l >= l && tr[u].r <= r){
tr[u].sum += (tr[u].r-tr[u].l)*val;
tr[u].lazy += val;
return;
}
pushdown(u);
int mid = (tr[u].l + tr[u].r) >> 1;
if (l <= mid) add(u<<1, l, r, val);
if (r >= mid+1) add (u<<1|1, l, r, val);
pushup(u);
}
代码:
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
LL w[N];
struct Node{
int l, r;
LL sum, lazy;
}tr[N]; //树中的节点!
int n, m;
void pushup(int u)//当执行到这里的时候,已经回溯了,表明此时的u为父节点。
{
tr[u].sum += tr[u<<1].sum + tr[u<<1|1].sum;
}
void pushdown(int u)
{
tr[u<<1].lazy += tr[u].lazy;
tr[u<<1].sum += (tr[u<<1].r - tr[u<<1].l + 1) * tr[u].lazy;
tr[u<<1|1].lazy += tr[u].lazy;
tr[u<<1|1].sum += (tr[u<<1|1].r - tr[u<<1|1].r + 1) * tr[u].lazy;
tr[u].lazy = 0;
}
void build(int u, int l, int r) //树节点编号,当前这个节点的左右端点!
{
tr[u] = {l, r};
if(l == r){
tr[u].sum = w[r]; //加上叶子节点权值。
return ;
}
int mid = (l + r) >> 1;
build (u<<1, l, mid);
build (u<<1|1, mid+1, r);
//当执行到这里的时候,已经回溯了,表明此时的u为父节点。
pushup(u); //回溯更新,下面一点会讲到!
}
void add (int u, int l, int r, int val)
{
if (tr[u].l >= l && tr[u].r <= r){
tr[u].sum += (tr[u].r-tr[u].l)*val;
tr[u].lazy += val;
return;
}
pushdown(u);
int mid = (tr[u].l + tr[u].r) >> 1;
if (l <= mid) add(u<<1, l, r, val);
if (r >= mid+1) add (u<<1|1, l, r, val);
pushup(u);
}
int query (int u, int pos)
{
if (tr[u].l == pos && tr[u].r == pos){
return tr[u].sum;
}
pushdown(u);
int mid = (tr[u].l + tr[u].r) >> 1;
LL sum = 0;
if (pos <= mid) sum = query(u<<1, pos);
if (pos >= mid + 1) sum += query(u<<1|1, pos);
return sum;
}
int main()
{
cin >> n >> m;
for(int i = 1;i <= n;++ i) cin >> w[i];
build(1, 1, 100000);
char op;
for(LL i = 1, l, r, x;i <= m;++ i)
{
cin >> op >> l;
if(op == 'C')
{
cin >> r >> x;
add(1, l, r, x);
}
else cout << query(1, l) << endl;
}
return 0;
}