题意
给出一个长度为NNN的序列AAA,有MMM次操作,有以下两种:
CCC xxx yyy zzz,把序列从xxx到yyy加上zzz
QQQ xxx yyy,查询序列xxx到yyy的和。
思路
一、树状数组
我们用bbb的前缀和代表AiA_iAi要增加多少,那么序列AAA的前缀和A[1∼x]A[1 \sim x]A[1∼x]一共增加的和就为:
∑i=1x∑j=1ib[i]\sum_{i=1}^{x}\sum_{j=1}^{i}b[i]∑i=1x∑j=1ib[i]
这个式子可以改成:
(x+1)∑i=1xb[i]−∑i=1xi∗b[i](x+1)\sum_{i=1}^{x}b[i]-\sum_{i=1}^{x}i*b[i](x+1)∑i=1xb[i]−∑i=1xi∗b[i]
这个公式可以画图理解,详见某蓝书,用树状数组维护一下这两个值就好了。
二、线段树
用一个延迟标记我们就可以完成区间修改的操作,其它的基本操作。
三、分块
把这个序列分成N\sqrt NN块,查找和判断时判断是不是在同一块,在同一块就可以直接累加答案。
如果是一整块的我们就可以直接用addaddadd来标记这一块增加了多少,两边有多余的话我们就可以直接暴力过去查找或修改,细节操作详见代码。
代码
树状数组
#include<cstdio>
#define ll long long
int n, q, a[100001], x, y, z;
ll b_1[100001], b_2[100001], s[100001];
char c;
ll ask(ll b[], int x) {
ll r = 0;
for (; x; x -= x & -x) r += b[x];
return r;
}
void change(ll b[], int x, int v) {
for (; x <= n; x += x & -x) b[x] += v;
return;
}
int main() {
scanf("%d %d", &n, &q);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
s[i] = s[i - 1] + a[i];
}
while (q--) {
c = getchar();
while (c != 'C' && c != 'Q') c = getchar();
if (c == 'Q') {
scanf("%d %d", &x, &y);
printf("%lld\n", (s[y] + (y + 1) * ask(b_1, y) - ask(b_2, y)) - (s[x - 1] + x * ask(b_1, x - 1) - ask(b_2, x - 1)));
}
else {
scanf("%d %d %d", &x, &y, &z);
change(b_1, x, z);
change(b_1, y + 1, -z);
change(b_2, x, z * x);
change(b_2, y + 1, -(y + 1) * z);
}
}
return 0;
}
线段树
#include<cstdio>
#define lson(p) (p) << 1
#define rson(p) lson(p) | 1
struct treenode{
int l, r;
long long sum, add;
}t[400001];
int n, q, a[100001], x, y, z;
char c;
void build(int p, int l, int r) {
t[p].l = l; t[p].r = r;
if (l == r) {
t[p].sum = a[l];
return;
}
int mid = (l + r) >> 1;
build(lson(p), l, mid);
build(rson(p), mid + 1, r);
t[p].sum = t[lson(p)].sum + t[rson(p)].sum;
return;
}
void spread(int p) {//下传延迟标记
if (t[p].add) {
t[lson(p)].sum += t[p].add * (t[lson(p)].r - t[lson(p)].l + 1);
t[rson(p)].sum += t[p].add * (t[rson(p)].r - t[rson(p)].l + 1);
t[lson(p)].add += t[p].add;
t[rson(p)].add += t[p].add;
t[p].add = 0;
}
return;
}
long long ask(int p, int l, int r) {
if (l <= t[p].l && t[p].r <= r) return t[p].sum;
spread(p);
int mid = (t[p].l + t[p].r) >> 1;
long long result = 0;
if (l <= mid) result += ask(lson(p), l, r);
if (r > mid) result += ask(rson(p), l, r);
return result;
}
void change(int p, int l, int r, int v) {
if (l <= t[p].l && t[p].r <= r) {
t[p].sum += (long long) v * (t[p].r - t[p].l + 1);
t[p].add += v;
return;
}
spread(p);
int mid = (t[p].l + t[p].r) >> 1;
if (l <= mid) change(lson(p), l, r, v);
if (r > mid) change(rson(p), l, r, v);
t[p].sum = t[lson(p)].sum + t[rson(p)].sum;
return;
}
int main() {
scanf("%d %d", &n, &q);
for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
build(1, 1, n);
while (q--) {
c = getchar();
while (c != 'Q' && c != 'C') c = getchar();
scanf("%d %d", &x, &y);
if (c == 'Q') printf("%lld\n", ask(1, x, y));
else {
scanf("%d", &z);
change(1, x, y, z);
}
}
}
分块
#include<cmath>
#include<cstdio>
#include<iostream>
using namespace std;
int n, m, t, q, x, y, pos[100001], l[100001], r[100001];
long long a[100001], z,sum[100001], add[100001];
char c;
void change(int x, int y, long long v) {
int p = pos[x], q = pos[y];
if (p == q) {
for (int i = x; i <= y; i++) a[i] += v, sum[p]+=v;
return;
}
for (int i = p + 1; i < q; i++) add[i] += v;
for (int i = x; i <= r[p]; i++) a[i] += v, sum[p]+=v;//暴力
for (int i = l[q]; i <= y; i++) a[i] += v, sum[q]+=v;
}
long long ask(int x, int y) {
int p = pos[x], q = pos[y];
long long result = 0;
if (p == q) {
for (int i = x; i <= y; i++) result += a[i]+add[p];
return result;
}
for (int i = p + 1; i < q; i++)
result += sum[i] + add[i] * (long long)(r[i] - l[i] + 1);
for (int i = x; i <= r[p]; i++) result += a[i] + add[p];//暴力
for (int i = l[q]; i <= y; i++) result += a[i] + add[q];
return result;
}
int main() {
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i++) scanf("%lld", &a[i]);
t = sqrt(n);
for (int i = 1; i <= t; i++) {
l[i] = (i - 1) * t + 1;//记录每一块的左端点
r[i] = i * t;//右端点
}
if (r[t] < n) l[++t] = r[t - 1] + 1, r[t] = n;//不足再补一块
for (int i = 1; i <= t; i++)
for (int j = l[i]; j <= r[i]; j++) {
pos[j] = i;
sum[i] += a[j];
}
while (m--) {
while (c = getchar(), c != 'Q' && c != 'C');
scanf("%d %d", &x, &y);
if (c == 'C') {
scanf("%lld", &z);
change(x, y, z);
}
else printf("%lld\n", ask(x, y));
}
}
博客围绕长度为N的序列A的M次操作展开,操作包括区间加值和查询区间和。介绍了三种解决思路,树状数组通过维护特定公式的值来处理;线段树利用延迟标记完成区间修改;分块则将序列分块,根据情况累加答案或标记修改,还给出了对应代码。
866

被折叠的 条评论
为什么被折叠?



