树状数组专题(自用)

树状数组考虑题时其实和前缀和的思想一样,但树状数组可以更改单个值从而更改整个前缀和的值,而且不用一一枚举,减少时间复杂度。

树状数组的集中思想是将整个下标划分成一串串的二进制数字,再通过这些二进制数字进行存储,修改等操作

AcWing 241. 楼兰图腾  

这题的思维从前往后枚举把当前结点之前比他大的,比他小的,存储下来。接着从后往前枚举得到比他大的,比他小的乘之前的就得到了该结点为上节点或者说下节点的数量。

#include<bits/stdc++.h>
using namespace std;
const int N=200005;
typedef long long LL;
LL l[N],r[N];
int a[N],st[N],n;
int lowbit(int x){//寻找二进制x最后一位是1的 
	return x&(-x);
}
void add(int x,int c){//给某个数加上c 
	for(int i=x;i<=n;i+=lowbit(i)){
		st[i]+=c;
	}
	return ;
}
LL sum(int x){
	int res=0;
	for(int i=x;i;i-=lowbit(i)){
		res+=st[i];
	}
	return res;
}
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	} 
	for(int i=1;i<=n;i++){
		int j=a[i];
		r[i]=sum(n)-sum(j);//大于 
		l[i]=sum(j-1);//小于
		add(j,1); 
	}
	memset(st,0,sizeof st);
	LL ant=0,pos=0;
	for(int i=n;i;i--){
		int j=a[i];
		ant+=(LL)(sum(n)-sum(j))*r[i];
		pos+=(LL)(sum(j-1))*l[i];
		add(j,1);
	}
	cout<<ant<<' '<<pos<<endl;
	return 0;
} 

AcWing 1215. 小朋友排队(蓝桥杯辅导课)

这题难点不在于树状数组,难点在于数学推导:
那个小朋友的身高排序我们根据冒泡排序可以得到最少交换时每交换一次就相当于减少一个逆序对,那么每个逆序对就代表着有两个小朋友的交换次数得加一,那么就相当于求每一个小朋友前面关于他交换的所有逆序对加上后面的关于他交换的所有逆序对就是这个小朋友自己本身交换的次数。然后求逆序对,用归并还是树状数组都可。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=1000010;
int a[N],st[N],n,p[N];
LL ace(int x){//根据交换次数求不满值
    LL k=(LL)(1+x)*x/2;
    return k;
}
//树状数组模型
int lowbit(int x){
    return x&(-x);
}
void add(int x,int c){
    for(int i=x;i<N;i+=lowbit(i)){
        st[i]+=c;
    }
}
int sum(int x){
    int res=0;
    for(int i=x;i;i-=lowbit(i)){
        res+=st[i];
    }
    return res;
}
int main(){
    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        a[i]++;
    }
    for(int i=1;i<=n;i++){
        int j=a[i];
        p[i]=p[i]+sum(N-1)-sum(j);//求前面所有大于他的逆序对
        add(j,1);
    }
    memset(st,0,sizeof st);
    for(int i=n;i;i--){
        int j=a[i];
        p[i]+=sum(j-1);//求后面所有小于他的逆序对
        add(j,1);
    }
    LL sum=0;//用long long,用int会爆空间
    for(int i=1;i<=n;i++){
        sum+=ace(p[i]);
    }
    cout<<sum<<endl;
    return 0;
}

AcWing 243. 一个简单的整数问题2(算法提高课)

区间更改求区间和模型:

这个题关键在于差分和前缀和的转化,如下图所示:

 通过图可以看到:差分的前缀和×(n+1)-关于(i*差分)的前缀和

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=100050;
int n,m;
int a[N];
LL str1[N],str2[N];
int lowbit(int x){
	return x&(-x);
}
void add(LL st[],int x,int c){
	for(int i=x;i<=n;i+=lowbit(i)){
		st[i]+=c;
	}
}
LL sum(LL st[],int x){
	LL res=0;
	for(int i=x;i;i-=lowbit(i)){
		res+=st[i];
	}
	return res;
}
LL ace(int x){
	return (LL)((x+1)*sum(str1,x))-(LL)(sum(str2,x));//公式
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		int b=a[i]-a[i-1];//先求数列的差分在进行存储
		add(str1,i,(LL)b);//str1存储差分值
		add(str2,i,(LL)b*i);//str2存储差分值乘i的值
	}
	while(m--){
		char s;
		int l,r,c;
		cin>>s>>l>>r;
		if(s=='Q'){
			cout<<ace(r)-ace(l-1)<<endl;
		}else{
			cin>>c;
			add(str1,l,c),add(str1,r+1,-c);//维护差分值
			add(str2,l,(LL)c*l),add(str2,r+1,(LL)-c*(r+1));//维护差分乘i的值 
		}
	}
	return 0;
}

  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值