AcWing 243. 一个简单的整数问题2
\(AcWing\) \(243\). 一个简单的整数问题2
一、题目描述
给定一个长度为 \(N\) 的数列 \(A\),以及 \(M\)条指令,每条指令可能是以下两种之一:
C l r d
,表示把 \(A[l],A[l+1],…,A[r]\) 都加上 \(d\)。
Q l r
,表示询问数列中第 \(l∼r\)个数的和。
对于每个询问,输出一个整数表示答案。
输入格式
第一行两个整数 \(N,M\)。
第二行 \(N\)个整数 \(A[i]\)。
接下来 \(M\) 行表示 \(M\)条指令,每条指令的格式如题目描述所示。
输出格式
对于每个询问,输出一个整数表示答案。
每个答案占一行。
数据范围
\(1≤N,M≤105\),\(|d|≤10000,|A[i]|≤10^9\)
二、树状数组
区间修改,单点查询
如果是区间修改,单点查询。只需用树状数组维护一个差分数组\(b\),假设查询位置\(x\),那么\(\displaystyle \sum_{i=1}^{x}b_i\)就是\(x\)位置上的变化后的值。
区间修改+区间和查询
考虑引入区间查询。首先最暴力想,假设查询\([1,r]\)。那么\([1,r]\)的答案=\(\displaystyle \sum_{i=1}^{r}\sum_{j=1}^{i}b_j\)
不妨举个特例,更直观些。假设查询\([1, 4]\)。那么\(ans=(b_1)+(b_1+b_2)+(b_1+b_2+b_3)+(b_1+b_2+b_3+b_4)=4b_1+3b_2+2b_3+1b_4\)。
换成查询\([1, r]\)。那么
三、树状数组
#include <bits/stdc++.h>
using namespace std;
const int N = 1000010;
// 树状数组
typedef long long LL;
#define lowbit(x) (x & -x)
LL c1[N], c2[N];
void add(LL x, LL d) {
for (LL i = x; i < N; i += lowbit(i)) c1[i] += d, c2[i] += x * d;
}
LL sum(LL x) {
LL res = 0;
for (LL i = x; i; i -= lowbit(i)) res += (x + 1) * c1[i] - c2[i];
return res;
}
int n, m;
LL a[N];
int main() {
// 加快读入
ios::sync_with_stdio(false), cin.tie(0);
#ifndef ONLINE_JUDGE
freopen("243.in", "r", stdin);
#endif
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i];
add(i, a[i] - a[i - 1]); // 保存基底是差分数组的树状数组
}
while (m--) {
string op;
int x, y, d;
cin >> op;
if (op[0] == 'Q') {
cin >> x >> y;
printf("%lld\n", sum(y) - sum(x - 1));
} else {
cin >> x >> y >> d;
add(x, d), add(y + 1, -d); // 维护差分
}
}
return 0;
}
代码细节解读
void add(LL x, LL v) {
for (LL i = x; i < N; i += lowbit(i)) c1[i] += v, c2[i] += x * v;
}
LL sum(LL x) {
LL res = 0;
for (LL i = x; i; i -= lowbit(i)) res += (x + 1) * c1[i] - c2[i];
return res;
}
概念说明
-
① \(b[i]=a[i]-a[i-1]\) : 原数组的差分数组
-
② \(c_1[]\):差分数组\(b[]\)对应的, 用于快速同步修改,快速统计分析 用的 树状数组,利用树状数组前缀和的特性,快速获取到区间修改后的单点值。
-
③ \(c_2[]\)维护的是\(i * b[i]\)的前缀和
修改流程
当我们给\(a[i] \sim a[j]\)加上\(d\)时,对应\(c_1[]\)就是差分的 双点 修改:
add(x,d),add(y+1,-d);
对应的内部逻辑就是辅助数组\(c_2[]\),它是差分数组\(i*b[i]\)的前缀和,原数组修改一下,它就跟着修改一下:
for (LL i = x; i < N; i += lowbit(i)) c1[i] += v, c2[i] += x * v;
而在查询时,按推出的公式查询即可
for (LL i = x; i; i -= lowbit(i)) res += (x + 1) * c1[i] - c2[i];
四、线段树+区间修改+懒标记+区间和查询
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 100010;
int n, m;
int a[N];
struct Node {
int l, r;
LL sum, tag; // 区间总和,修改的数值(懒标记)
} tr[N << 2];
// 向祖先节点更新统计信息
void pushup(int u) {
tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum; // 向父节点更新sum和
}
// 父节点向子节点传递懒标记
void pushdown(int u) {
auto &root = tr[u], &ls = tr[u << 1], &rs = tr[u << 1 | 1];
if (root.tag) { // 如果存在懒标记
// tag传递到子段,子段的sum和需要按 区间长度*root.tag 进行增加
ls.tag += root.tag, ls.sum += (LL)(ls.r - ls.l + 1) * root.tag;
rs.tag += root.tag, rs.sum += (LL)(rs.r - rs.l + 1) * root.tag;
// 清除懒标记
root.tag = 0;
}
}
// 构建
void build(int u, int l, int r) {
tr[u] = {l, r}; // 标记范围
if (l == r) { // 叶子
tr[u] = {l, r, a[l], 0}; // 区间内只有一个元素l(r),区间和为a[l],不需要记录向下的传递tag
return;
}
int mid = (l + r) >> 1;
build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r); // 左右儿子构建
pushup(u); // 通过左右儿子构建后,向祖先节点反馈统计信息变化
}
// 以u为根,在区间[l,r]之间全都增加v
void modify(int u, int l, int r, int v) {
if (tr[u].l >= l && tr[u].r <= r) { // 如果区间完整命中
tr[u].sum += (LL)(tr[u].r - tr[u].l + 1) * v; // 总和增加 = 区间长度*v
tr[u].tag += v; // 懒标记+v
return;
}
pushdown(u); // 如果自己身上有旧的tag数值,在递归前需要将原tag值pushdown到子孙节点去
int mid = (tr[u].l + tr[u].r) >> 1;
if (l <= mid) modify(u << 1, l, r, v); // 与左区间有交集
if (r > mid) modify(u << 1 | 1, l, r, v); // 与右区间有交集
pushup(u); // 将结果的变更更新到祖先节点
}
// 关键的查询操作
LL query(int u, int l, int r) {
if (tr[u].l >= l && tr[u].r <= r) return tr[u].sum;
// 记住原则:有懒标记的区间修改,都是先pushdown消除掉懒标记,再分裂
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
LL sum = 0;
if (l <= mid) sum = query(u << 1, l, r);
if (r > mid) sum += query(u << 1 | 1, l, r);
// 左查+ 右查 = 总和
return sum;
}
int main() {
// 加快读入
ios::sync_with_stdio(false), cin.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> a[i];
// 构建线段树
build(1, 1, n);
char op;
int l, r, d;
while (m--) {
cin >> op >> l >> r;
if (op == 'C') {
cin >> d;
modify(1, l, r, d); // 区间修改
} else
printf("%lld\n", query(1, l, r)); // 区间查询
}
return 0;
}