树状数组
初识树状数组
树状数组用的是树结构的思想(也就是树型逻辑结构),而不是真正的“树形结构”,(换句话说,从某种意义上,树状数组跟树其实没有特别大的关系)
- 树状数组支持的操作:
区间和、区间异或和、区间乘积和RMQ(显然,支持的操作都具有交换律)
2.单点修改和区间修改。 树状数组的优点:树状数组的维护是(logn)的,并且查询、修改是O(1)。 树状数组被称为树的原因:维护以(logn),而这个log底是2;就是二进制表示法,也就是二叉树上数据之间的特殊逻辑关系。
还是不懂的人可以结合下面的图理解一下啦不要关闭换下一个QWQ
1: 1
2: 10
3: 11
4: 100
5: 101
6: 110
7: 111
8:1000
而对于每一个x的最低含一位,即上文中的2,可以借助一个lowbit函数实现——而这个的实现方式是很玄学的
我也看不懂
简单的说就是用这个函数作为线往根(上)走
幼体代码解释
主要最原始的树状数组是单点修改,区间查询
lowbit函数
inline int lowbit(int x) {
//return (x^(x-1))&x;
return x&(-x);
}
// inline这个是让函数不进入在用的地方直接代替函数里的内容
// 还是不懂就把它当做很加快运行速度就行了QWQ
- 两种实现方法:一种是通过数学+二进制的方式,另一种则是通过计算机编码特性
- 记住第二种就对了
应该也没有人用第一种吧QWQ
树状数组的建立维护和查询
建立维护:此处拿求区间和为样例
inline void search(int x, int k) { // 在x点加上k
for(; x <= n; x += lowbit(i))
tree[x] += k;
}
查询:
inline int query(int x) {// 求1~x的和
int ans = 0;
for(; x; x -= lowbit(x))
ans += tree[x];
return ans;
}
求和:
inline int add(int x, int y) { // 1~y 减 1~(x-1) 可得 x~y
return query(y) - query(x-1);
}
幼体模板
- lougu P3374
- 将某一个数加上 x
- 求出某区间每一个数的和
- 输入
- 1 x k 含义:将第 x 个数加上 k
- 2 x y 含义:输出区间 [x,y] 内每个数的和
#include<iostream>
using namespace std;
const int MAX = 5*1e5 + 10;
int tree[MAX];
int n , m;
inline int lowbit(int k) {
return k & -k;
}
inline void search(int x, int k) {// 修改
for(; x <= n; x += lowbit(x))
tree[x] += k;
}
inline int query(int x) {// 求1~x的和
int ans = 0;
for(; x; x-=lowbit(x))
ans += tree[x];
return ans;
}
inline int add(int x, int y) {
return query(y) - query(x-1);
}
int main() {
scanf("%d%d", &n, &m);
int a, x, y;
for(int i = 1; i <= n; ++i) {
scanf("%d", &a);
search(i,a); // 将值放入树状数组
}
for(int i = 1; i <= m; ++i) {
scanf("%d%d%d", &a, &x, &y);
if(a == 1) search(x, y);
if(a == 2) cout << add(x, y) << endl;
}
return 0;
}
成熟体代码解释
进阶树状数组区间修改,单点查询
这个树状数组主要在于存的不是输入的值,而是输入的值的与上一个值的差分
scanf("%d%d", &n, &m);
ll a, b, x, y;
b = 0;
for(int i = 1; i <= n; ++i) {
scanf("%lld", &a);
b = a - b; // 差分
// D[i] = C[i] - C[i-1];
search(i, b); // 将差分放进数组
b = a;
}
主要差别就在这个
用差分的话, 在进行区间加的时候,就不用在区间内每个数都加一遍,而是在这个区间的头和尾加上值,这样的话就可以将整个区间都与其他数相差这个值了;
文字说明好像也很难懂qwq,那我们还是结合文字看图吧
很简单的吧
也因为C[i] = D[1] + D[2] + … + D[i]
所以查询一个数直接用这个就可以啦
inline ll query(ll x) { // 用差分查点D[1~x]得C[i]
ll ans = 0;
for(; x > 0; x-=lowbit(x))
ans += tree[x];
return ans;
}
成熟体模板
#include<iostream>
using namespace std;
const int MAX = 5*1e5 + 10;
typedef long long ll;
ll tree[MAX];
int n , m;
// tree[i] = D[i]
inline ll lowbit(ll k) {
return k & -k;
}
inline void search(ll x, ll k) {// 修改
for(; x <= n; x += lowbit(x))
tree[x] += k;
}
inline ll query(ll x) { // 用差分查点
ll ans = 0;
for(; x > 0; x-=lowbit(x))
ans += tree[x];
return ans;
}
int main() {
scanf("%d%d", &n, &m);
ll a, b, x, y;
b = 0;
for(int i = 1; i <= n; ++i) { // 这里变化一下
scanf("%lld", &a);
b = a - b;
search(i, b);
b = a;
}
for(int i = 1; i <= m; ++i) {
scanf("%lld", &a);
if(a == 1) {
scanf("%lld%lld%lld", &x, &y, &b);
search(x, b); // 这里要改两个数
search(y+1, -b); // 分别是头和尾
}
if(a == 2) {
scanf("%lld", &b);
cout << query(b) << endl; // 不用相减了
}
}
return 0;
}
完全体代码解释
既然是完全版,那肯定是既能区间修改又能区间查询啦
首先有这一个公式证明
只要这个公式懂了 其他就会了
由成体版我们可以知道D[1]+D[2]+……D[i]=C[i]
那么,对于1到r的区间和,即为:
*- C[1]+C[2]+……+C[r-1]+C[r] //用上方公式推导得出
=D[1]+(D[1]+D[2])+……+(D[1]+……+D[r]) //根据加法交换律与结合律:
=(D[1](r ))+(D[2](r-1))+……(D[r]1) //那么:
=r(D[1]+D[2]+……+D[r])-(D[1]0+D[2]1+……+D[r](r-1))
看到这里,是不是已经很清晰了呢?
对于C的树状数组(差分)tree,建立一个新的树状数组tree1使得:
tree1[i]=tree[i]*(i-1)
( tree[i] = D[i] tree1[i] = D[i] * (i-1) )
之后,x到y的区间和即为:
( y*query(tree,y)-(x-1)*query(tree,x-1))-(query(tree1,y)-query(tree1,x-1) )
Tips:
因为求区间和满足区间加法,所以Sum(L,R)=Sum(1,R)-Sum(1,L-1),所以有上述公式。
如果还是看不懂公式 那我们就看看图了
i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|
C[i] | 1 | 2 | 4 | 6 | 8 | 4 | 6 | 7 | 10 |
D[i] | 1 | 1 | 2 | 2 | 2 | -4 | 2 | 1 | 3 |
D[i]*(i-1) | 0 | 1 | 4 | 6 | 8 | -20 | 12 | 7 | 24 |
修改区间2~6 加 5 后得 | |||||||||
i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
– | – | – | – | – | – | – | – | – | – |
C[i] | 1 | 7 | 9 | 11 | 13 | 9 | 6 | 7 | 10 |
D[i] | 1 | 6 | 2 | 2 | 2 | -4 | -3 | 1 | 3 |
D[i]*(i-1) | 0 | 6 | 4 | 6 | 8 | -20 | -18 | 7 | 24 |
查询区间 2~8 | |||||||||
sum = ( 8*(D[1]+…D[8]) - 1*(D[1]) ) - ( D[1](1-1)+…+D[8](8-1) - D[1]*(1-1) ) |
= ( D[1]*7 + D[2]*7 + D[3]*6+...D[8]*1 )
= ( C[2] + C[3] + ... C[8] )
= 62
用( y*query(tree,y)-(x-1)*query(tree,x-1))-(query(tree1,y)-query(tree1,x-1) )也可以得
sum = ( 8*(D[1]+…D[8]) - 1*(D[1]) ) - ( D[1](1-1)+…+D[8](8-1) - D[1](1-1) )
= (8C[8] - C[1]) - ( D[1](1-1)+…+D[8](8-1) - D[1]*(1-1) )
= (56 - 1) - (6 + 4 + 6 + 8 - 20 -18 + 7 + 24)
= 55 - (-7) = 62
完全体模板
#include<iostream>
using namespace std;
const int MAX = 5*1e5 + 10;
typedef long long ll;
ll tree[MAX];
ll tree1[MAX];
int n , m;
// tree[i] = D[i],tree1[i] = D[i]*(i-1);
inline ll lowbit(ll k) {
return k & -k;
}
inline void search(ll *f, ll x, ll k) {// 修改
for(; x <= n; x += lowbit(x))
f[x] += k;
}
inline ll query(ll *f, ll x) {
ll ans = 0;
for(; x > 0; x-=lowbit(x))
ans += f[x];
return ans;
}
inline ll add(int x, int y) {
ll ans1 = y*query(tree, y) - (x-1)*query(tree,x-1);
ll ans2 = query(tree1, y) - query(tree1, x-1);
return ans1 - ans2;
}
int main() {
scanf("%d%d", &n, &m);
ll a, b, x, y;
b = 0;
for(int i = 1; i <= n; ++i) {
scanf("%lld", &a);
b = a - b;
search(tree , i, b);
search(tree1, i, (i-1)*b);
b = a;
}
for(int i = 1; i <= m; ++i) {
scanf("%lld", &a);
if(a == 1) {
scanf("%lld%lld%lld", &x, &y, &b);
search(tree, x, b);// 区间修改
search(tree, y+1, -b);
search(tree1, x, (x-1)*b);
search(tree1, y+1, y*(-b));
}
if(a == 2) {
scanf("%lld%lld", &x, &y);
cout << add(x, y) << endl; // 区间和
}
}
return 0;
}
萌新初来写博客,如有错误, 求大佬手下留情QWQ