树状数组学习笔记
树状数组学习笔记
树状数组的使用场景:可差分且有结合律的运算符,如 ^ + *
树状数组优点:编码简单,常数小,但思维量较大。
板子:
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) 的时间复杂度,那如果有多组询问,那就很麻烦了,因为这里的 N 是 n 的最大值,但如果$\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<L,ri<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;
}