标签:ISE names 位置 type 一点 公式 使用 query std
这篇文章前半部分主要研究树状数组的区间更新 单点求值
,后半部分研究区间更新 区间求和
。
树状数组的基本知识以及单点更新区间求和,差分的思想。
回顾一下最简单的树状数组的功能:快速求出一个数列中某个数的前缀和,以及修改一个位置上的数。
现在我们要利用这两个功能实现:快速求出某个数列中某个数的值,以及修改一个区间上的数。
结合差分的知识,将上下两种功能进行比对,不难发现:快速求出某个数列中某个数的值,就是在该数列的差分数列中求出前缀和(差分和前缀和是互逆操作);而区间更新则是差分最擅长的,只需要进行两次单点修改即可。
模板题是洛谷P3368 树状数组2。
在原本的树状数组代码中做一些小改动(已经标注出),得到:
#include<bits/stdc++.h>
using namespace std;
int n;
int c[1000010];
void rev(int st, int fi, int x); // 运用差分进行区间修改
int query(int loc); // 查询单点值(不是前缀和!)
void _pointRev(int loc, int x);
inline int lowbit(int x);
int main(void) {
int m;
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i) {
int x;
scanf("%d", &x);
rev(i, i, x);
}
for (int i = 1; i <= m; ++i) {
int f, st, fi, x;
scanf("%d", &f);
switch(f) {
case 1:
scanf("%d%d%d", &st, &fi, &x);
rev(st, fi, x);
break;
case 2:
scanf("%d", &st);
printf("%d\n", query(st));
}
}
return 0;
}
void rev(int st, int fi, int x) {
_pointRev(st, x); // 首加
if (fi < n) { // 有可能是末尾,严谨一点
_pointRev(fi + 1, -x); // 尾后一位减
}
return;
}
int query(int loc) {
int sum = 0;
for (int i = loc; i > 0; i -= lowbit(i)) {
sum += c[i];
}
return sum;
}
void _pointRev(int loc, int x) {
for (int i = loc; i <= n; i += lowbit(i)) {
c[i] += x;
}
return;
}
inline int lowbit(int x) {
return x & -x;
}
区间求和要通过前缀和来进行(树状数组也只能维护前缀和)。而区间更新又必须使用差分,用普通的方法就行不通了。我们来列出公式进行一下变形(num[]表示原数组,c[]表示差分数组):
num[1]+num[2]+num[3]+...+num[n]
=(c[1]) + (c[1]+c[2]) + (c[1]+c[2]+c[3]) + ... + (c[1]+c[2]+c[3]+...+c[n])
=c[1]*n + c[2]*(n-1) + c[3]*(n-2) + ... + c[n]*1
=c[1]*(n-0) + c[2]*(n-1) + c[3]*(n-2) + ... + c[n]*(n-(n-1))
=(c[1] + c[2] + c[3] + ... + c[n]) * n - c[1]*0 - c[2]*1 - c[3]*2 - ... - c[n] * (n-1)
=(c[1] + c[2] + c[3] + ... + c[n]) * n - (c[1]*(1-1) + c[2]*(2-1) + c[3] * (3-1) + ... + c[n] * (n-1))
最终得到的公式需要维护一个对于c[i]的普通前缀和以及一个对于c[i]*(i-1)的前缀和变体,两个树状数组就可以满足了。
模板题是洛谷P3372 线段树1
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
int n;
LL c1[100010], c2[100010];
void revise(int st, int fi, LL x);
LL query(int st, int fi);
void _pointRevise(int loc, LL x);
LL _pointQuery(int loc);
inline int lowbit(int x);
int main(void) {
int m;
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i) {
LL t;
scanf("%lld", &t);
revise(i, i, t);
}
for (int i = 1; i <= m; ++i) {
int opt, x, y;
LL k;
scanf("%d", &opt);
switch(opt) {
case 1:
scanf("%d%d%lld", &x, &y, &k);
revise(x, y, k);
break;
case 2:
scanf("%d%d", &x, &y);
printf("%lld\n", query(x, y));
break;
}
}
return 0;
}
void revise(int st, int fi, LL x) {
_pointRevise(st, x);
if (fi < n) {
_pointRevise(fi + 1, -x);
}
return;
}
LL query(int st, int fi) {
return _pointQuery(fi) - ((st > 1) ? _pointQuery(st - 1) : 0);
}
void _pointRevise(int loc, LL x) {
for (int i = loc; i <= n; i += lowbit(i)) {
c1[i] += x;
c2[i] += x * (loc - 1);
}
return;
}
LL _pointQuery(int loc) {
LL sum = 0;
for (int i = loc; i > 0; i -= lowbit(i)) {
sum += c1[i] * loc - c2[i];
}
return sum;
}
inline int lowbit(int x) {
return x & -x;
}
标签:ISE names 位置 type 一点 公式 使用 query std
原文地址:https://www.cnblogs.com/KevinYao-Blog/p/KevinYao1224-Blog.html