树状数组Ⅰ


树状数组很有用,虽然是不如线段树那么强大,但是它写起来简单得不得了,而且它的复杂度是赤裸裸的\(O(\log n)\),没有讨厌的常数!

盗张图来说明树状数组的工作原理(图片来自oi-wiki):

树状数组的功能为单点修改区间查询,或者区间修改单点查询。

懒得废话上模板:

  • 单点修改区间查询:

struct tree{
	#define lowbit(wh) (wh&-wh)
   int c[N],m;
	void change(int wh,int num){
		for(;wh<=m;wh+=lowbit(wh))c[wh]+=num;
	}
	int work_sum(int wh){
		int ans=0;
		for(;wh;wh-=lowbit(wh))ans+=c[wh];
		return ans;
	}
};

其中change函数可以做到在\(O(\log n)\)的复杂度下给第wh个元素加上num,work_sum函数可以做到在\(O(\log n)\)的复杂度下求出前wh个元素的和(包括第wh个元素。)

  • 区间修改单点查询(与差分结合):

struct tree{
	#define lowbit(wh) (wh&-wh)
   int c[N],m;
	void change1(int wh,int num){
		for(;wh<=m;wh+=lowbit(wh))c[wh]+=num;
	}
	void change2(int l,int r,int num){
		change1(l,num);
		change1(r+1,-num);
	}
	int work(int wh){
		int ans=0;
		for(;wh;wh-=lowbit(wh))ans+=c[wh];
		return ans;
	}
};

其中change2函数可以做到\(O(\log n)\)的复杂度下给第l个和第r个元素之间(包含两端)的所有元素加上num,而work函数可以在\(O(\log n)\)的复杂度下求出第wh个元素的值。


树状数组的作用

  • 快速求区间和,不说了都是模板。

  • 求逆元或者变相逆元,因为是树状数组可以快速求出比当前数小(或者大)的数的个数,也可以在\(O(n\log n)\)的复杂度下求逆元以及一些更加复杂的问题。

比如 三元上升子序列,归并排序就无法解决了,就要用到树状数组。

思路就是暴力,每处理一个数就扫描前面的数,遇到一个比它小的数就统计比这个数小的数的个数,而这个“比这个数小的个数”可以用树状数组维护,所以复杂度就是\(O(n^2\log^2 n)\)

它依然可以优化,因为“比这个数小的数”所有的答案区间会有很严重的重叠,所以可以另开一个树状数组r,它动态更新,维护当前(假如已经处理到第now个元素了)满足\(a_i\(i的个数。

然后复杂度就可以降到\(O(n\log^2 n)\),(因为每个元素要查找离散化之后的位置有个log的二分查找复杂度)

代码(注意离散化):

#include
#include
#include
#define int long long
#define sc(wh) scanf("%lld",&wh)
using namespace std;
const int N=30010;

int m,a[N],b[N],c[N];
int find(int wh){
	int l=1,r=m,mid,an;
	while(l<=r){
		mid=l+r>>1;
		if(a[mid]