给出 N N 个数 Ai A i , Q个操作 Q 个 操 作 ,每次操作有 2 2 种方式,
①给出区间 [l,r] [ l , r ] 以及一个数 x x ,表示给 区间[l,r] 区 间 [ l , r ] 中的每个 Ai A i 都加上一个 x x
②给出 区间[l,r] 区 间 [ l , r ] ,回答区间 [l,r] [ l , r ] 的数的总和
1≤N,Q≤105 1 ≤ N , Q ≤ 10 5
−108≤Ai≤108 − 10 8 ≤ A i ≤ 10 8
−104≤x≤104 − 10 4 ≤ x ≤ 10 4
这题其实有多种解决方法,
①分块:
我们将序列 A A 分成 ⌊N−−√⌋ ⌊ N ⌋ 块,
每一块的长度都是 ⌊N−−√⌋ ⌊ N ⌋ ,除了最后一块不确定。
这个显然。
我们设
Add[i]表示第i块中的每个数都能被加上的数 A d d [ i ] 表 示 第 i 块 中 的 每 个 数 都 能 被 加 上 的 数
Sum[i]表示第i块的区间和(不包括Add[i]) S u m [ i ] 表 示 第 i 块 的 区 间 和 ( 不 包 括 A d d [ i ] )
Orz[j]表示第j个数增加的数(不包括j所属于的块i的Add[i]) O r z [ j ] 表 示 第 j 个 数 增 加 的 数 ( 不 包 括 j 所 属 于 的 块 i 的 A d d [ i ] )
显然 Orz O r z 的初值就是序列 A A ,
而 Sum[i]的初值可以通过扫一遍得出 S u m [ i ] 的 初 值 可 以 通 过 扫 一 遍 得 出
而后面的插入 x x 的操作,
就是在区间 [l,r]中 [ l , r ] 中 将他们之间能被完全覆盖的块的 Add[] A d d [ ] 加上 x x
然后对于l跟r所归属的块单独操作 然 后 对 于 l 跟 r 所 归 属 的 块 单 独 操 作 ,
即
[l,l所属块的右端点] [ l , l 所 属 块 的 右 端 点 ] 中的所有 Orz[] O r z [ ] 加上 x x
[r所属块的左端点,r] [ r 所 属 块 的 左 端 点 , r ] 中的所有 Orz[] O r z [ ] 加上 x x
此时对应的 Sum[i]需要更新 S u m [ i ] 需 要 更 新
然后注意一下当 l,r l , r 在同一块的时候,
直接扫一遍 Orz[]更新 O r z [ ] 更 新 即可
查询的时候也很简单,
[l,r] [ l , r ] 之间的全部块,直接 将Add[]跟Sum[] 将 A d d [ ] 跟 S u m [ ] 计入答案即可
而对于头尾的块,扫一遍 Orz[] O r z [ ] ,且累加所属块的 Orz[] O r z [ ] 就可以了
同样要注意一下 l,r l , r 在同一个块的情况
总的思想就是“大段维护、局部朴素”
时间复杂度: O((N+Q)∗N−−√) O ( ( N + Q ) ∗ N )
②树状数组:
在利用树状数组完成这题之前,
要先去解决用树状数组+差分实现的“区间增加+单点查询”到“单点增加+区间查询”的做法
题目链接:
https://www.luogu.org/problemnew/show/P3368
好了,我们设
当前的差分数组是d[] 当 前 的 差 分 数 组 是 d [ ]
当前的序列A的前缀和数组Sum[] 当 前 的 序 列 A 的 前 缀 和 数 组 S u m [ ]
那么题目显然对于一个 [l,r] [ l , r ] ,
显然求的就是
Sum[r]−Sum[l−1] S u m [ r ] − S u m [ l − 1 ] + ∑ri=l∑ij=1d[j] ∑ i = l r ∑ j = 1 i d [ j ]
Sum[r]−Sum[l−1] S u m [ r ] − S u m [ l − 1 ] 是已知的,
我们考虑一下 ∑ri=l∑ij=1d[j] ∑ i = l r ∑ j = 1 i d [ j ]
显然可以对于一个
∑ri=1∑ij=1d[j] ∑ i = 1 r ∑ j = 1 i d [ j ] (这里i是从1开始不是l) ( 这 里 i 是 从 1 开 始 不 是 l )
可以转化成 ∑ri=1(r−i+1)∗d[i] ∑ i = 1 r ( r − i + 1 ) ∗ d [ i ]
进一步转化就是
(r+1)∑ri=1d[i]−∑ri=1i∗d[i] ( r + 1 ) ∑ i = 1 r d [ i ] − ∑ i = 1 r i ∗ d [ i ]
那么我们就可以利用两个树状数组 S1,S2 S 1 , S 2 分别维护 d[i] d [ i ] 和 i∗d[i] i ∗ d [ i ] 的前缀和
每次的区间 [l,r] [ l , r ] 的修改,都可以变成对于差分数组 d[l]和d[r+1] d [ l ] 和 d [ r + 1 ] 的修改,
即 d[l]加上x,d[r+1]减去x d [ l ] 加 上 x , d [ r + 1 ] 减 去 x
那么我们所维护的前缀和中,
只需要在树状数组中稍微修改,即
S1的位置l加上x,位置r+1减去x S 1 的 位 置 l 加 上 x , 位 置 r + 1 减 去 x
S2的位置l加上l∗x,位置r+1减去(r+1)∗x S 2 的 位 置 l 加 上 l ∗ x , 位 置 r + 1 减 去 ( r + 1 ) ∗ x
然后我们每次查询 [l,r]的区间和的时候 [ l , r ] 的 区 间 和 的 时 候 ,
我们可以通过树状数组求出
∑ri=1d[i] ∑ i = 1 r d [ i ]
∑ri=1i∗d[i] ∑ i = 1 r i ∗ d [ i ]
那么
(r+1)∑ri=1d[i]−∑ri=1i∗d[i] ( r + 1 ) ∑ i = 1 r d [ i ] − ∑ i = 1 r i ∗ d [ i ]
就是
∑ri=1∑ij=1d[j] ∑ i = 1 r ∑ j = 1 i d [ j ]
同理可以求出
∑li=1∑ij=1d[j] ∑ i = 1 l ∑ j = 1 i d [ j ]
他们的差就是
∑ri=l∑ij=1d[j] ∑ i = l r ∑ j = 1 i d [ j ]
加上一开始的 Sum[r]−Sum[l−1] S u m [ r ] − S u m [ l − 1 ]
就可以实现查询了
时间复杂度: O((N+Q)logN) O ( ( N + Q ) l o g N )
③线段树:
这种方法,就很容易实现跟理解了,
自行百度吧,
注意一下这题要打Lazy标记即可
①分块:
#include
#include
#include
#include
#include
#define N 200005
using namespace std;
typedef long long LL;
struct Node {
int l, r;
LL Sum;
}Cp[505];
LL Belong[N], Add[N], Op[N], num;
int ans, n, m, tot;
void Insert(int l, int r) {
int x = Belong[l], y = Belong[r];
if (x == y) {
for (int i = l; i <= r; i++) Op[i] += num;
Cp[x].Sum += (r - l + 1) * num;
return;
}
for (int i = x + 1; i <= y - 1; i++) Add[i] += num;
for (int i = l; i <= Cp[x].r; i++) Op[i] += num;
for (int i = Cp[y].l; i <= r; i++) Op[i] += num;
Cp[x].Sum += (Cp[x].r - l + 1) * num;
Cp[y].Sum += (r - Cp[y].l + 1) * num;
}
LL Get_Sum(int l, int r) {
int x = Belong[l], y = Belong[r];
LL Rp = 0;
if (x == y) {
for (int i = l; i <= r; i++) Rp += Op[i];
Rp += Add[x] * (r - l + 1);
} else {
for (int i = x + 1; i <= y - 1; i++)
Rp += Cp[i].Sum + Add[i] * (Cp[i].r - Cp[i].l + 1);
for (int i = l; i <= Cp[x].r; i++) Rp += Op[i];
for (int i = Cp[y].l; i <= r; i++) Rp += Op[i];
Rp += Add[x] * (Cp[x].r - l + 1);
Rp += Add[y] * (r - Cp[y].l + 1);
}
return Rp;
}
int main() {
scanf("%d %d", &n, &m);
int Rp = sqrt(n);
tot = 0;
for (int i = 1; i <= n; i++) {
scanf("%lld", &num);
if (i <= tot * Rp) Cp[tot].r = i;
else tot++, Cp[tot].l = i;
Cp[tot].Sum += num;
Belong[i] = tot;
Op[i] = num;
}
Cp[tot].r = n;
while (m--) {
char str[2];
int l, r;
scanf("%s %d %d", str, &l, &r);
if (str[0] == 'C') {
scanf("%lld", &num);
Insert(l, r);
} else printf("%lld\n", Get_Sum(l, r));
}
return 0;
}
②树状数组:
#include
#include
#include
#include
#define N 100005
using namespace std;
typedef long long LL;
LL c[2][N], sum[N];
int n, m;
int lowbit(int x) {
return x & (-x);
}
void Insert(int k, int x, int num) {
while (x <= n) {
c[k][x] += num;
x += lowbit(x);
}
}
LL Getsum(int k, int x){
LL rp = 0;
while (x) {
rp += c[k][x];
x -= lowbit(x);
}
return rp;
}
int main() {
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i++) {
int x;
scanf("%d", &x);
sum[i] = sum[i - 1] + x;
}
while (m--) {
char str[2];
int l, r, x;
scanf("%s %d %d", str, &l, &r);
if (str[0] == 'C'){
scanf("%d", &x);
Insert(0, l, x);
Insert(1, l, l * x);
Insert(0, r + 1, - x);
Insert(1, r + 1, - (r + 1) * x);
} else {
LL ans = (sum[r] - sum[l - 1]) +
((r + 1) * Getsum(0, r) - Getsum(1, r)) -
(l * Getsum(0, l - 1) - Getsum(1, l - 1));
printf("%lld\n", ans);
}
}
return 0;
}
③线段树:
#include
#include
#include
#include
#define N 500005
using namespace std;
typedef long long LL;
LL Tree[N], Lazy[N], num, ans;
int n, m;
void Insert(int W, int l, int r, int x, int y) {
if (l == x && r == y) {
Tree[W] += (r - l + 1) * num;
Lazy[W] += num;
return;
}
int mid = (l + r) >> 1;
Tree[W * 2] += (mid - l + 1) * Lazy[W];
Tree[W * 2 + 1] += (r - mid) * Lazy[W];
Lazy[W * 2] += Lazy[W];
Lazy[W * 2 + 1] += Lazy[W];
Lazy[W] = 0;
if (y <= mid) Insert(W * 2, l, mid, x, y);
else if (x > mid) Insert(W * 2 + 1, mid + 1, r, x, y);
else {
Insert(W * 2, l, mid, x, mid);
Insert(W * 2 + 1, mid + 1, r, mid + 1, y);
}
Tree[W] = Tree[W * 2] + Tree[W * 2 + 1];
}
void Count_Sum(int W, int l, int r, int x, int y) {
if (l == x && r == y) {
ans += Tree[W];
return;
}
int mid = (l + r) >> 1;
Tree[W * 2] += (mid - l + 1) * Lazy[W];
Tree[W * 2 + 1] += (r - mid) * Lazy[W];
Lazy[W * 2] += Lazy[W];
Lazy[W * 2 + 1] += Lazy[W];
Lazy[W] = 0;
if (y <= mid) Count_Sum(W * 2, l, mid, x, y);
else if (x > mid) Count_Sum(W * 2 + 1, mid + 1, r, x, y);
else {
Count_Sum(W * 2, l, mid, x, mid);
Count_Sum(W * 2 + 1, mid + 1, r, mid + 1, y);
}
}
int main() {
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i++) {
scanf("%lld", &num);
Insert(1, 1, n, i, i);
}
while (m--) {
char str[2];
int l, r;
scanf("%s %d %d", str, &l, &r);
if (str[0] == 'C') {
scanf("%lld", &num);
Insert(1, 1, n, l, r);
} else if (str[0] == 'Q') {
ans = 0;
Count_Sum(1, 1, n, l, r);
printf("%lld\n", ans);
}
}
return 0;