树状数组学习笔记

树状数组学习笔记

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

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

板子:

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; }

__EOF__

本文作者火牛
本文链接https://www.cnblogs.com/hnczy/p/18706011.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   hnczy  阅读(10)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 对象命名为何需要避免'-er'和'-or'后缀
· JDK 24 发布,新特性解读!
· C# 中比较实用的关键字,基础高频面试题!
· .NET 10 Preview 2 增强了 Blazor 和.NET MAUI
· SQL Server如何跟踪自动统计信息更新?
点击右上角即可分享
微信分享提示