树状数组学习笔记

树状数组学习笔记

树状数组的使用场景:可差分且有结合律的运算符,如 ^ + *

树状数组优点:编码简单,常数小,但思维量较大。

板子:

struct B_tree {
	int c[N];
	void clear(){
		memset(c,0,sizeof c); 
	}
	void add(int x,int v) {
		for (int i = x; i <= n; i += lowbit(i)) c[i] +=v;
	}
	int query(int x) {
		int ans = 0;
		for (int i = x; i; i -= lowbit(i)) ans += c[i];
		return ans;
	}
	int query1(int l,int r) {
		return query(r)-query(l-1);
	}
}bit;

这里着重讲一下拓展内容

1. 权值树状数组

很简单,就是跟前缀和用数值做下标一样的。

2. 时间戳优化

普通的 clear 需要 O(N) 的时间复杂度,那如果有多组询问,那就很麻烦了,因为这里的 Nn 的最大值,但如果$\sum n $ 较小,那就可以用时间戳优化(但是常数会稍大一点),其实就是把每一个点记一下当前的版本,每一次 clear 都更新一下版本。

struct B_tree {
	int c[N],t[N],T;
	void clear() {
		T++;
	}
	inline int lowbit(int x) {
		return x&-x;
	}
	inline void add(int x,int v) {
		while(x<=tot) {
			if(t[x]==T)c[x]+=v;
			else t[x]=T,c[x]=v;
			x+=lowbit(x);
		}
	}
	inline int query(int x) {
		int res=0;
		while(x) {
			if(t[x]==T)res+=c[x];
			x-=lowbit(x);
		}
		return res;
	}
	inline int query(int l,int r) {
		return query(r)-query(l-1);
	}
} bit;

3.二维偏序/三维偏序

二维偏序就是变相的逆序对,也就是两个限制条件 如 li<Lri<R 这样的条件就可以二维偏序。

二维偏序的主要步骤:先根据第一维排序,在用一个树状数组解决。当然,后一维需要进行离散化。

为什么呢?因为你排序之后的值就相当于逆序对里的下标。

这个其实不算很难理解。代码实现也非常的简单,这里就不放了。

三维偏序呢?

这就需要用cdq分治了 。

cdq分治就是一排序,二归并,三树状的做法。

即先用排序来消除一维影响,再用归并排序做出第二维的影响,在使用一个树状数组来解决即可。

因为你归并排序之后就相当于消除了第二维的影响,那再用一个树状数组就可以实现三维偏序了。

核心代码:

void cdq(int l,int r) {
	if(l==r)return;
	int mid=l+r>>1;
	cdq(l,mid),cdq(mid+1,r);
	for(int i=l,L=l,R=mid+1; i<=r; i++) {
		if(R>r||a[L].y >=a[R].y&&L<=mid) {
			u[i]=a[L++];//与归并不同的地方
			if(!u[i].id)bit.add(u[i].x,1);
		} else {
			u[i]=a[R++];
			if(u[i].id)cnt[u[i].id]+=bit.query(u[i].x);//与归并不同的地方
		}
	}
	for(int i=l; i<=r; i++)a[i]=u[i];
	bit.clear();
}

这里建议点和询问分开处理,比较简洁,这里的有无 id 就代表是不是点。

#include<bits/stdc++.h>
using namespace std;
const int N=8e5+5;
int n,tot,cnt[N],b[N];
struct node {
	int x,y,z,id,res;
} a[N],u[N];
struct B_tree {
	int c[N],t[N],T;
	void clear() {
		T++;
	}
	inline int lowbit(int x) {
		return x&-x;
	}
	inline void add(int x,int v) {
		while(x<=tot) {
			if(t[x]==T)c[x]+=v;
			else t[x]=T,c[x]=v;
			x+=lowbit(x);
		}
	}
	inline int query(int x) {
		int res=0;
		while(x) {
			if(t[x]==T)res+=c[x];
			x-=lowbit(x);
		}
		return res;
	}
	inline int query(int l,int r) {
		return query(r)-query(l-1);
	}
} bit;
bool cmp(node a,node b) {
	if(a.z!=b.z )return a.z>b.z;
	return a.id <b.id;
}
void cdq(int l,int r) {
	if(l==r)return;
	int mid=l+r>>1;
	cdq(l,mid),cdq(mid+1,r);
	for(int i=l,L=l,R=mid+1; i<=r; i++) {
		if(R>r||a[L].y >=a[R].y&&L<=mid) {
			u[i]=a[L++];
			if(!u[i].id)bit.add(u[i].x,1);
		} else {
			u[i]=a[R++];
			if(u[i].id)cnt[u[i].id]+=bit.query(u[i].x);
		}
	}
	for(int i=l; i<=r; i++)
		a[i]=u[i];
	bit.clear();
}
int main() {
	scanf("%d",&n);
	for(int i=1,l,r,k; i<=n; i++) {
		scanf("%d%d%d",&l,&r,&k);
		a[i]= {l,r,r-l,0};
		a[i+n]= {r-k,l+k,k,i};
		b[++tot]=l;
		b[++tot]=r-k;
	}
	sort(b+1,b+tot+1);
	tot=unique(b+1,b+tot+1)-b-1;
	for(int i=1; i<=2*n; i++)
		a[i].x=lower_bound(b+1,b+tot+1,a[i].x)-b;
	sort(a+1,a+2*n+1,cmp);//对z排序
	cdq(1,2*n);
	for(int i=1; i<=n; i++)
		printf("%d\n",cnt[i]-1);
	return 0;
}

完整代码「USACO24OPEN G」Grass Segments

4.第k值

用一个二分的写法是O(log2n) 的,但是用倍增写就是 O(logn) 的。二分很简单,这里就不放了。

int kth(int k){
		int x=0;
		for(int i=log2(n);i>=0;i--){
			if(x+(1<<i)<n&&c[x+(1<<i)]<k){
				x+=(1<<i);
				k-=c[x];
			}
		}//类似一个倍增 
		return x+1;
	}
posted @ 2025-02-09 12:49  hnczy  阅读(10)  评论(0编辑  收藏  举报