树状数组——operator_

算法概述

O ( log ⁡ n ) O(\log n) O(logn) 的单点修改,区间询问(前提是操作可逆)

弱化版的线段树,但代码极其简单,时间常数极低,空间要求极低,而且更加灵活。

写法

1.定义

这个东西比较特殊,用图感觉反而会乱(主要是根节点不是 1 1 1),我就尽量用纯数学的讲吧。

定义 t [ i ] = a [ i ] + a [ i − 1 ] + ⋯ + a [ i − l o w b i t ( i ) + 1 ] t[i]=a[i]+a[i-1]+\dots+a[i-lowbit(i)+1] t[i]=a[i]+a[i1]++a[ilowbit(i)+1] ,反正就是前 l o w b i t ( i ) lowbit(i) lowbit(i) 个数的和。

其中 l o w b i t ( i ) lowbit(i) lowbit(i) 指的是 i i i 在二进制表示下最低位的 1 1 1 所代表的数量级,比如 10 = ( 1010 ) 2 10=(1010)_2 10=(1010)2 ,那么 l o w b i t ( 10 ) = 2 1 = 2 lowbit(10)=2^1=2 lowbit(10)=21=2

它听起来很复杂,但其实求法异常简单: l o w b i t ( i ) = i & − i lowbit(i)=i\&-i lowbit(i)=i&i 。以下简称 l b ( i ) lb(i) lb(i)

2.单点修改

发现其实树状数组是有递归性质的。

看这张图

是不是好像有什么规律又找不出来

直观感受一下,一个数字上叠了几层,就是它的 l b lb lb

比如 A [ 2 ] A[2] A[2] 上有 2 2 2 层,代表它的 l b lb lb 2 2 2

一个数加上它的 l b lb lb 就是它右边的第一个层数比它高的数。(即第一个 l b lb lb 大于它的 l b lb lb 的数)

一个数加上它的 l b lb lb 就是它左边的第一个层数比它高的数

怎么理解呢,从图形上讲就是说它有递归性质,从数学上讲减 l b lb lb 就是消除最后一个 1 1 1 ,加法就是消除的同时再进位,详细的话应该是可证的。

反正规律记牢就好了

考虑什么东西会影响这个即将修改的点。

发现就是它的父节点,即它右边的第一个层数比它高的数,不断加 l b lb lb 即可达到目的。

void add(int x,int k) {
	for(int i=x;i<=n;i+=lb(i)) 
    	t[i]+=k;
}

3.区间询问

因为操作可逆,所以其实询问的是单点前缀和。

发现肯定要向前循环,而一个子树的和就是该子树最右边的树状数组的值,即它左边的第一个层数比它高的数,那么就可以直接跳过该子树,又是它左边的第一个层数比它高的数,所以不断减 l b lb lb 并统计和即可达到目的。

int sum(int x) {
	int s=0;
	for(int i=x;i;i-=lb(i)) 
		s+=t[i];    
	return s;
}
int query(int x,int y) {
	return sum(y)-sum(x-1);
}

4.建树

因为一般的询问次数不会小于数组的容量,所以每个数都修改一遍即可,复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn) ,完全可以接受。

但也不排除某些特殊情况,所以也有一种 O ( n ) O(n) O(n) 建树的方法。

树状数组其实是一个二叉树(不过只存了最高层的值),而每个节点的其中一个儿子就是它自己,另一个儿子从父节点的角度不好找,但从儿子的角度就很好找了。(参照单点修改)

那么一定是 O ( n ) O(n) O(n) 的。

void build() {
	for(int i=1;i<=n;++i) {
		t[i]+=a[i];
		if(i+lb(i)<=n) 
			t[i+lb(i)]+=t[i];
	}
}

5.习题

太多了,先随便放 2 2 2 个模板。

P3374 【模板】树状数组 1

P3368 【模板】树状数组 2

2 2 2 题的话需要用树状数组存差分数组。

权值树状数组

算法概述:

树状数组这个东西嘛,就是一个超级弱化版的线段树,一般是用来维护动态前缀和。

那么考虑这样一个问题:求 1   i 1~i 1 i 中比 a i a_i ai 大的数的数量。

第一眼好像不能用树状数组维护,但事实上是可以的,因为有这样一个东西: 权值树状数组

权值树状数组的核心思路就是把权值作为下标,来维护一些东西。

经典的应用就是求 逆序对

例题:P1908 逆序对

考虑构造一个数组 d i d_i di ,表示 i i i 这个值,在目前出现的次数。

d d d 构造树状数组 t t t ,以维护前缀和,那么对于一个 a i a_i ai ,所有比 a i a_i ai 小或等于的值的出现次数就是 t t t s u m sum sum ,所以以 i i i 位置为末尾的逆序对数量就是前面所有比 a i a_i ai 大的值的出现次数,即 i − s u m i-sum isum

那么就可以 O ( n log ⁡ n ) O(n\log n) O(nlogn) 解决。

啊本题需要离散化,别忘了。

#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read() {
	int s=0,m=0;char ch=getchar();
	while(!isdigit(ch)) {if(ch=='-')m=1;ch=getchar();}
	while( isdigit(ch)) s=(s<<3)+(s<<1)+(ch^48),ch=getchar();
	return m?-s:s;
}
int n,a[500005],b[500005],q,ans;
struct BIT {
	int t[500005];
	int lb(int x) {return x&-x;}
	int sum(int x) {//单点前缀和询问
	    int s=0;
		for(int i=x;i;i-=lb(i)) s+=t[i];    
	    return s;
	}
	void add(int x,int k) {//单点修改
		for(int i=x;i<=500000;i+=lb(i)) 
			t[i]+=k;
	}
}t;
signed main() {
	cin>>n;
	for(int i=1;i<=n;i++)
		b[i]=a[i]=read();
	sort(b+1,b+n+1);
	q=unique(b+1,b+n+1)-b-1;
	for(int i=1;i<=n;i++)
		a[i]=lower_bound(b+1,b+q+1,a[i])-b;
	for(int i=1;i<=n;i++) {
		t.add(a[i],1);
		ans+=i-t.sum(a[i]);
	}
	cout<<ans;
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值