【模板篇】树状数组们(三)
UPD: 本篇有了一个更通(hui)俗(se)易(nan)懂的讲解, 大家可以移步这里围观~(使用了latex推柿子, 带给你不一样的清新体验~
ok,以上两期稍稍讲了一下树状数组的基本功能。。
- 最最基础——单点加,区间查 http://blog.csdn.net/enzymii/article/details/54952957 忘了的回去看哦
- 升级做法——区间加,单点查 http://blog.csdn.net/enzymii/article/details/54965280
当然,把树状数组拉出来不能只有这两个功能对不对。。。(不然网上都有怎么把你们忽悠来看嘛)
树状数组还是有两把刷子的
(非战斗人员退散)
今天,我们要讲的是:
区间加,区间查
什么???
许多学过线段树的人该诧异了吧。。
树状数组还能干这事?
答案是可以的。。
(○| ̄|_在此%拜一下发明这种做法的神犇orz)
还记得上次我们的c变化成了差分数组吗?
树状数组就是擅长求前缀和和维护差分信息~
当然,这次的目标中有两个区间字样,所以要开两个数组(什么逻辑嘛……)
其实原因并不是这样。
我们用一个差分数组来保存相邻两个数据的差,这个数组命名为c1
此时,无论我们在树状数组上怎么乱搞,原数据的第i个点最后的值(记为a[i]吧)就是求一下c1[i]的总和
(嗯我们上一期的单点查)
对于区间修改,我们采取让c1[l]加和让c1[r+1]减的方式(还是差分)
对于区间查询,我们有ans=sum[r]-sum[l-1](How old are you,差分?)
于是很明显,最后原数组中第i个点乱搞一波后的值是sigma(c1[j]) (j=1..i)
你们理清楚没有。。
理清楚之后,
求1~i的和的时候,仔细看下面:
sum(i)=a[1]+a[2]+...+a[i]
=c1[1]+(c1[1]+c1[2])+...+(c1[1]+c1[2]+...+c1[i])
=i*c1[1]+(i-1)*c1[2]+...+1*c1[i]
=i*(c1[1]+c1[2]+...+c1[i])-(0*c1[1]+1*c1[2]+...+(i-1)*c1[i])
=i*sigma(c1[j])-sigma(c1[j]*(j-1)) (j=1..i)
所以,我们只要同时 维护一下sigma(c1[j])和sigma(c1[j]*(j-1))就行了。。
还记得c1的差分性质么
所以我们再用一个数组c2搞出sigma(c1[j]*(j-1)),在维护c1的时候顺手维护一下即可。。
这样复杂度也不会被改变!!!非常好而且奇妙的性质。。。
sum[i]就照着上面的式子搞就行。。
下面,终于到了代码,我有一件事情要说:其实只看代码就好,上面讲的没啥用233
代码的例子是用的luogu3372的【模板】线段树 1 (哈哈哈哈,用线段树的人们!)
题目传送门:
https://www.luogu.org/problem/show?pid=3372
里面的数据是要用long long的。。
你们自己看情况改就好了233
而且听说codevs1082的线段树练习3也可以用这种方法水过。。
这题的传送门:http://www.codevs.cn/problem/1082/
而且码长空间时间都要优于线段树哦。。
不过main函数我就不写了_ (:з」∠) _
而且这两个题都要开long long而代码里是没有开的
#include <cstdio>
#define gc getchar
int getnum() {
int a = 0; char c = gc(); bool f = 0;
for (; (c<'0' || c>'9') && c != '-'; c = gc());
if (c == '-') f = 1, c = gc();
for (; c >= '0'&&c <= '9'; c = gc()) a = (a << 1) + (a << 3) + c - '0';
return f ? -a : a;
}
class Binary_Tree3 {
private:
static const int MAXN = 200002;
int c1[MAXN], c2[MAXN], n, a[MAXN];
inline int lb(int x) {
return x&-x;
}
int getnum() {
int a = 0; char c = gc(); bool f = 0;
for (; (c<'0' || c>'9') && c != '-'; c = gc());
if (c == '-') f = 1, c = gc();
for (; c >= '0'&&c <= '9'; c = gc()) a = (a << 1) + (a << 3) + c - '0';
return f ? -a : a;
}
public:
void build(int sum) {
n = sum;
for (int i = 1; i <= sum; i++) {
a[i] = getnum();
add(c1, i, a[i] - a[i - 1]);
add(c2, i, (i - 1) * (a[i] - a[i - 1]));
}
}
void add(int *r, int x, int i) {
for (; x <= n; x += lb(x))
r[x] += i;
}
int ask(int *r, int x) {
int s = 0;
for (; x; x -= lb(x))
s += r[x];
return s;
}
void adda(int l, int r, int i) {
add(c1, l, i); add(c1, r + 1, -i);
add(c2, l, i * (l - 1)); add(c2, r + 1, -i * r);
}
int query(int l, int r) {
return r * ask(c1, r) - ask(c2, r) - (l - 1) * ask(c1, l - 1) + ask(c2, l - 1);
}
};
大概就是这个样子了。。
- 区间加的话就调用adda(l,r,i)就是区间[l,r]加i
- 区间查的话就输出query(l,r)就是区间[l,r]的区间和了。。
对就是这样。