树状数组的基本操作
树状数组的基本操作
基本操作1:
求比a小在a前面数数量和比a小在a后面数数量:
在看之前,你必须了解树状数组的基本函数;
inline ll lowbit(ll x) { return x&(-x); } inline void insert(ll x,ll y)//加入 { while(x<=n) { sum[x]+=y; x+=lowbit(x); } } inline ll findout(ll x)//查找 { ll ans=0; while(x) { ans+=sum[x]; x-=lowbit(x); } return ans; }
求有多少在a前面的数比a小
首先,假如求有多少在a前面的数比a小;
举例: 1 4 2 3 5
然后有5个空位置, _ _ _ _ _ 为sum[5]
第一步 求出sum[1]前缀,答案是0;
插入1, 1 _ _ _ _
第二步 求出sum[4]前缀,答案是1;
插入4, 1 _ _ 4 _
第二步 求出sum[2]前缀,答案是1;
插入4, 1 2 _ 4 _
..........................
这样不断进行下去sum[i]就是 有多少在i前面的数比a小;
所以就转化成了求前缀和的题目了,自然就想到树状数组了;
但是输入的几个数可能会非常大;
我们只需知道每个数的大小关系,并不需要知道具体值,所以在处理之前可以离散化;
//离散化 for(ll i=1;i<=n;i++) { a[i].v=read(); a[i].num=i;//将每一个数的位置记下 } sort(a+1,a+n+1,cmp);//从小到大排序, //这样每个数都有顺序了,并且每个数对应的位置没有改变 for(ll i=1;i<=n;i++) b[a[i].num]=i;//把第i小的数位置上赋值为i
具体怎么实现,读者自行手动模拟;
按上面查找的思路
for(ll i=1;i<=n;i++) { ll x=findout(b[i]); ans[i]=x;//先求值,再插入,不然会把自己也算进去的 insert(b[i],1); }
那么求比a小在a后面数数量
则反之
for(ll i=n;i>=1;i--) { ll x=findout(b[i]); ans[i]=x;//反之 insert(b[i],1); }
这样就ok了
基本操作2:
求1-x的最大值
那么求1-x的最大值,就需要在findout函数中改一改就好了;
我们从基本操作1中代码可以看到;
ans+=sum[x];
是将每个sum[x]的权值相加(具体自己看上面,这里不细讲);
求1-x的最大值,只需将代码改成:
ans=max(ans,sum[x]);
insert函数中就需把 sum[x]+=y;改成 sum[x]=max(sum[x],y);(这个比较容易理解吧)
那么代码就是:
inline ll lowbit(ll x) { return x&(-x); } inline void insert(ll x,ll y)//加入 { while(x<=mx) { sum[x]=max(sum[x],y);// x+=lowbit(x); } } inline ll findout(ll x)//查找 { ll ans=0; while(x) { ans=max(ans,sum[x]);// x-=lowbit(x); } return ans; }
基本操作3:
查询第k大的数的大小
如果数据范围较大,也是需要先进行离散化的
思路是用二分+树状数组;
insert(x,y); //数x出现了y次,插入树状数组 ll l=1,r=n; while(l<=r) { ll mid=(l+r)>>1; if(findout(mid)>=k) r=mid-1; else l=mid+1; } //最后 a[l] 即是第k大的数