树状数组用于快速处理区间问题;
时间复杂度为logn
树状数组可以将长度为n的数组分解成logn个区间;
把n用二进制表示出来,n=2p1+2p2+…+2pm;其中m=logn;
长度为2p1的区间:[1,2p1];
长度为2p2的区间:[2p1+1,2p1 +2p2];
…
长度为2pm的区间:[2p1+2p2+…+1,2p1 +…2pm];
若区间以x结尾,则长度为lowbit(x);
用代码表示为:
while(x){
printf("[%d,%d]\n",x-lowbit(x)+1,x);
x-=lowbit(x);
}
每个c[x],表示以x为根的子树的所有节点的和;
节点个数为lowbit(x);
除了根节点。每个c[x]的父节点都是c[x+lowbit(x)];
整颗数的深度为logn
基本操作:
//更改单个节点数值;这里的单点更新值给a[x]的值加y!
//N根据情况看,c存数字是N表示数组长度,c存个数时表示最大的数值;
void add(int x, int y) {
for (; x <= N; x += lowbit(x)) {
c[x] += y;
}
}
//处理前缀和
int ask(int x) {
int sum = 0;
for (; x; x -= lowbit(x))
sum += c[x];
return sum;
}
//预处理,可以使用update操作;时间复杂度为nlogn;
//也可以先求出前缀和,然后预处理,时间复杂度为on;
void inti(){
for(int i=1;i<=n;i++){
c[i]=s[i]-s[i-lowbit(i)];
}
}
求逆序对;
c数组存储的为个数
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N=1000100;
int c[N],a[N];
int n;
int mmax;
#define lowbit(x) x&(-x)
void add(int x,int y){
while(x<=N){
c[x]+=y;
x+=lowbit(x);
}
}
int ask(int x){
ll sum=0;
while(x){
sum+=c[x];
x-=lowbit(x);
}
return sum;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]),mmax=max(mmax,a[i]);
ll ans=0;
for(int i=n;i>=1;i--) {
ans+=ask(a[i]-1);
add(a[i],1);
}
printf("%lld\n",ans);
return 0;
}
楼兰图腾
c数组存储个数
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
using namespace std;
typedef long long ll;
int n;
const int N=200010;
int c[N],a[N],g[N],l[N];
#define lowbit(x) x&(-x)
void add(int x,int y){
for(;x<=n;x+=lowbit(x))
c[x]+=y;
}
int ask(int x){
int sum=0;
for(;x;x-=lowbit(x))
sum+=c[x];
return sum;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++){//正序跑得到的一定时1~i之间的;
int y=a[i];
l[i]=ask(y-1);
g[i]=ask(n)-ask(y);
add(y,1);
}
ll res1=0,res2=0;
memset(c,0,sizeof c);
for(int i=n;i>=1;i--){//倒序跑得到的时i~n之间
int y=a[i];
res1+=(ll)l[i]*ask(y-1);
res2+=(ll)g[i]*(ask(n)-ask(y));
add(y,1);
}
cout<<res2<<' '<<res1<<endl;
return 0;
}
拓展:差分树状数组;
基本的树状数组可以求区间和以及单点修改;
对于区间修改的时候可以利用差分数组,即树状数组存储原数组的差分数组,区间和就是原数组的值;
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 100010;
int a[N], c[N], p[N];
#define lowbit(x) x&(-x)
void add(int x, int y) {
for (; x <= N; x += lowbit(x)) c[x] += y;
}
int ask(int x) {
int res = 0;
for (; x; x -= lowbit(x)) res += c[x];
return res;
}
int main() {
int n, q;
cin >> n >> q;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int i = 1; i <= n; i++) {
p[i] = a[i] - a[i - 1];//差分数组;
add(i, p[i]);
}
string op;
int l, r;
int x;
while (q--) {
cin >> op;
if (op[0] == 'C') {
cin >> l >> r >> x;
add(l, x);//差分数组区间增减操作;
add(r + 1, -x);//同上;
} else {
cin >> l;
cout << ask(l) << endl;
}
}
return 0;
}
扩展2:更改连续区间,求连续区间的和;
先求出差分数组,维护差分数组b的前缀和以及b*i的前缀和
黑色部分就是就是原数组的前缀和,其中b是差分数组,可以先将整个矩阵的值求出来,然后可得
S
n
=
(
n
+
1
)
∗
∑
i
=
1
n
b
[
i
]
−
∑
i
=
1
n
b
[
i
]
∗
i
Sn=(n+1)*\sum_{i=1}^{n}b[i]-\sum_{i=1}^{n}b[i]*i
Sn=(n+1)∗∑i=1nb[i]−∑i=1nb[i]∗i
Sn就是黑色部分的前缀和
;
注意维护两个前缀和时需要同步修改!
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 100010;
#define lowbit(x) x&(-x)
typedef long long ll;
ll a[N],c1[N],c2[N];
void add(ll c[],int x,ll y){
for(;x<=N;x+=lowbit(x)) c[x]+=y;
}
ll ask(ll c[],int x){
ll res=0;
for(; x;x-=lowbit(x)) res+=c[x];
return res;
}
ll get_sum(int x){
return (ll)ask(c1,x)*(x+1)-ask(c2,x);
}
int main(){
int n,q;
cin>>n>>q;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++){
int d=a[i]-a[i-1];
add(c1,i,d),add(c2,i,(ll)i*d);
// add(c1,r+1,-d),add(c2,r+1,(ll)(r+1)*(-d));
}
string op;
int l,r,d;
while(q--){
cin>>op>>l>>r;
if(op[0]=='C'){
cin>>d;
add(c1,l,d),add(c2,l,(ll)l*d);
add(c1,r+1,-d),add(c2,r+1,(ll)(r+1)*(-d));
}else{
cout<<get_sum(r)-get_sum(l-1)<<endl;
}
}
return 0;
}
具体修改某一个值为x
利用差分数组;给指定位置的值增加为x-a[i];其中a[i]可以通过ask(i)来求;
void change(int i,int x){
int k=ask(c1,i);//通过差分数组前缀和求得原数组得值;
// add(c2,i,100-k);
`相当于给区间长度为1的区间加上这个差值`
add(c1,i,x-k);
add(c1,i+1,k-x);
}