树状数组学习笔记

树状数组,可以用于处理前缀和,前缀积,前缀最大值等等

相比于直接处理前缀和,优势在于修改和查询都是logn的,但是功能有限

树状数组通过将每一组数分为多块,提前处理好部分的和,在求值时根据特定的路径去查询

注意由于树状数组使用lowbit,所以不能直接处理0相关的问题

区间处理

在进行区间查询时,如果具有可逆性,那么可以直接将从r的查询结果和l-1的进行逆操作

但是对于不能逆操作的,比如求最大值,通常需要两个log,不如线段树

在进行区间加法时,要同时给一个区间加一个数,可以有两种方法

第一种,单点查询,只用在树状数组中装每两个之间的差值即可

然而,如果要区间查询,则需要使用区间修改法

具体方法是设两个数组,修改时代码:

void add(int k, int v) {
  int v1 = k * v;
  while (k <= n) {
    t1[k] += v, t2[k] += v1;
    // 注意不能写成 t2[k] += k * v,因为 k 的值已经不是原数组的下标了
    k += lowbit(k);
  }
}

查询时代码:

long long getsum1(int l, int r) {
  return (r + 1ll) * getsum(t1, r) - 1ll * l * getsum(t1, l - 1) -
         (getsum(t2, r) - getsum(t2, l - 1));
}

其余部分找常即可

第k小值

首先将所有数输入进来,进行离散化

第一种方法是二分第k小值,然后在树状数组中进行查找,两个log

然鹅还要方法可以在一个log内完成

考虑倍增的思想,由于树状数组的性质,可以倍增实现

代码:

// 权值树状数组查询第 k 小
int kth(int k) {
  int sum = 0, x = 0;
  for (int i = log2(n); ~i; --i) {
    x += 1 << i;                    // 尝试扩展
    if (x >= n || sum + t[x] >= k)  // 如果扩展失败
      x -= 1 << i;
    else
      sum += t[x];
  }
  return x + 1;
}

二维偏序问题

使用树状数组,可以很方便地处理二维偏序问题

由于存在两个参考量,可以先将其中一条安装加入时间的顺序进行排序,然后通过树状数组按照前缀后缀的顺序查找,即可得出符合两个条件的其他项的数量了

常见的问题包括逆序对,由于需要按照初步排列的顺序,加入时间就为从后往前,这边处理是每一个逆序对找后面的一项

然后加权时直接将值离散化后的结果加入,然后即可处理出来

#include<bits/stdc++.h>
using namespace std;
int n,a[100005],b[100005];
int lowbit(int x){
	return x&-x;
}
struct BT{
	int c[100005];
	void add(int x,int y){
		for(int i=x;i<=n;i+=lowbit(i))c[i]+=y;
	}
	int query(int x){
		int ans=0;
		for(int i=x;i>=1;i-=lowbit(i))ans+=c[i];
		return ans;
	}
}bit;
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i];
	for(int i=1;i<=n;i++)b[i]=a[i];
	sort(b+1,b+n+1);
	unique(b+1,b+n+1);
	for(int i=1;i<=n;i++)a[i]=lower_bound(b+1,b+n+1,a[i])-b;
	int ans=0;
	for(int i=n;i>=1;i--){
		ans+=bit.query(a[i]-1);
		bit.add(a[i],1);
	}
	cout<<ans;
	return 0;
}

树状数组的离线处理

在问题是类似于某个点只出现一次时,由于每个点在范围不同时贡献不同,但是不能每次都进行预处理,否则必定会超时

考虑能否直接沿用上之前预处理后的结果

因此在这类题目中,通常含有r和l两个值,安装r处理完成以后,再通过l在树状数组中得到答案即可

示例代码丹钓战

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,q,cnt,pre[500005],a[500005],b[500005],ans[500005];
struct P{
	int a,b,id;
}p[500005];
struct Q{
	int l,r,id;
}c[500005];
bool cmp(Q a,Q b){
	return a.r<b.r;
}
int lowbit(int x){
	return x&-x;
}
struct BT{
	int c[500005];
	void init(){
		memset(c,0,sizeof(c));
	}
	void add(int x,int y){
		x++;
		for(int i=x;i<=n;i+=lowbit(i))c[i]+=y;
	}
	int query(int x){
		x++;
		int ans=0;
		for(int i=x;i>=1;i-=lowbit(i))ans+=c[i];
		return ans;
	}
	int query(int l,int r){
		return query(r)-query(l-1);
	}
}bit;
signed main(){
	scanf("%lld%lld",&n,&q);
	for(int i=1;i<=n;i++)scanf("%lld",a+i);
	for(int i=1;i<=n;i++)scanf("%lld",b+i);
	for(int i=1;i<=n;i++){
		while(cnt!=0&&!(p[cnt].a!=a[i]&&p[cnt].b>b[i])){
			cnt--;
		}
		if(!cnt)pre[i]=0;
		else pre[i]=p[cnt].id+1;
		p[++cnt]={a[i],b[i],i};
	}
	for(int i=1;i<=q;i++){
		scanf("%lld%lld",&c[i].l,&c[i].r);
		c[i].id=i;
	}
	sort(c+1,c+q+1,cmp);
	int w=0;
	for(int i=1;i<=q;i++){
		while(w<c[i].r){
			w++;
			bit.add(pre[w],1);
		}
		ans[c[i].id]=bit.query(c[i].l)-c[i].l+1;
	}
	for(int i=1;i<=q;i++)printf("%lld\n",ans[i]);
	return 0;
}
posted @ 2025-11-28 10:02  huhangqi  阅读(0)  评论(0)    收藏  举报
/*
*/