转载:https://blog.csdn.net/I_believe_CWJ/article/details/80374326
例:HDU 1556-Color the ball
操作1:修改某段区间[l, r]的值
操作2:求某项的值
前面我们简单介绍了BIT的作用:可以快速求出某段区间的值,但是我们如何快速修改某段区间的值呢?
这里我们引入另一个数组,叫做差分数组b[],这个数组内装的是什么值呢?
下面给大家解释一下,这里我们的b[i] = a[i]-a[i-1]那么我们的a[i] = b[1]+b[2]+b[3]+....+b[i],所以我们求某个单点的值就又转化成了一个区间求和。
例如:a[] = 1 3 5 9 7 10
那么:b[] = 1 2 2 4 -2 3
那么我们进行区间更新的时候怎么操作呢?
同样对于上面的那个例子,我们要给区间[2,4]内的每一项加1,那么此时我们的b[] = 1 3 2 4 -3 3,与原来的b[]数组相比,由于差分的效果,我们的b[]数组是不是只是把b[2]+1,b[5]-1,除了b[l]和b[r+1],其他地方的差值没有变呐,所以对于区间更新+单点求值的问题,我们又转化成了单点更新+区间求和的问题啦!
求和:所以我们求a[i]就求b[]数组的sum(i)即可
更新:我们更新a[l, r]+x就add(l, x), add(r+1, -x)即可
#include
#include
#include
#include
#define bug printf("*********\n");
#define mem0(a) memset(a, 0, sizeof(a));
#define mem1(a) memset(a, -1, sizeof(a));
using namespace std;
typedef long long LL;
int bit[100010], n;
int sum(int i)
{
int ans = 0;
while(i > 0) {
ans += bit[i];
i -= (i&-i);
}
return ans;
}
void add(int i, int k)
{
while(i <= n) {
bit[i] += k;
i += (i&-i);
}
}
int main()
{
int l ,r;
while(~scanf("%d", &n) && n) {
mem0(bit); //因为开始所有的差分值都为0
/*如果有初值的话,就
a[0] = 0
for(int i = 1; i <= n; i ++) {
scanf("%d", &a[i]);
add(i, a[i] - a[i-1]);
}
*/
for(int i = 0; i < n; i ++) {
scanf("%d%d", &l, &r);
add(l, 1);
add(r+1, -1);
}
for(int i = 1; i < n; i ++)
printf("%d ", sum(i));
printf("%d\n", sum(n));
}
return 0;
}
例题:POJ 3468-A Simple Problem with Integers
操作1:给区间[l, r]的所有数加上x
操作2:求区间[l ,r]内的和
前面我们实现了区间更新+单点求和,那么神仙们一定不会放过区间更新+区间求和的骚操作,确实,大神们也实现了这一操作,接下来我就给大家讲解一下,其实也是非常的简单:
前面的区间更新+单点求值,我们的a[i] = b[1] + b[2] + b[3] +....+ b[i],那么我们推一下区间求和
我们来看看a[1] + a[2] + a[3] + ... + a[n] = b[1] + (b[1]+b[2]) + (b[1]+b[2]+b[3]) + ... + (b[1]+b[2]+b[3]+...+b[n])
= n*(b[1]+b[2]+b[3]+...+b[n]) - (0*b[1]+1*b[2]+2*b[3]+...+(n-1)*b[n])
那么我们在定义一个数组c[i] = (i-1)*b[i],那么我们的区间[1, n]的a[i]和就为n*sum(b[] ,n) - sum(c[], n)
我们在修改b[i]时同时对c[i]进行维护即可,即add(b[], i, x) 的同时 add(c[], i, (i-1)x)
例如我们要求a[l] ~ a[r]的和,那么我们用b[]数组来代替所有的a[]会是什么样呢?
结果就是:( n*sum(b[], r) - sum(c[], r) ) - ( n*sum(b[], l) - sum(c[], l) )。
#include
#include
#include
#include
#define mem0(a) memset(a, 0, sizeof(a));
#define mem1(a) memset(a, -1, sizeof(a));
using namespace std;
typedef long long LL;
LL n, m, a[100010];
LL bit0[100010], bit1[100010];
char str[2];
LL sum(LL *bit, LL k)
{
LL ans = 0;
while(k > 0) {
ans += bit[k];
k -= (k & -k);
}
return ans;
}
void add(LL *bit, LL k, LL add)
{
while(k <= n) {
bit[k] += add;
k += (k & -k);
}
}
int main()
{
LL l, r, w;
while(~scanf("%lld%lld", &n, &m)) {
mem0(bit0);
mem0(bit1);
a[0] = 0;
for(int i = 1; i <= n; i ++) {
scanf("%lld", &a[i]);
add(bit0, i, a[i] - a[i-1]);
add(bit1, i, (i-1)*(a[i] - a[i-1]));
}
while(m --) {
scanf("%s", str);
if(str[0] == 'C') {
scanf("%lld%lld%lld", &l, &r, &w);
add(bit0, l, w);
add(bit0, r+1, -w);
add(bit1, l, (l-1)*w);
add(bit1, r+1, -r*w);
}else {
scanf("%lld%lld", &l, &r);
LL ans = 0;
ans -= (l-1)*sum(bit0,l-1)-sum(bit1,l-1);
ans += r*sum(bit0,r)-sum(bit1,r);
printf("%lld\n", ans);
}
}
}
return 0;
}
看了所有树状数组的代码,相比之下,线段树的代码是不是就显得格外的长,还容易出错,下面贴出最后一个例题的线段树AC的代码,大家比较下就。。。
#include
#include
#include
#include
#include
#include