【树状数组·进阶篇】树状数组实现平衡树(树状数组上二分)

\(Preface\)

余未曾想到,树状数组居然是一个这么高端的东西。

从未想到过树状数组上还可以二分,从未想到过树状数组还能实现平衡树的功能。

唔姆,树状数组果然也是美丽的,余喜欢一切美丽的东西。

树状数组上二分

众所周知,树状数组上的\(a_i\)维护的是\(\sum_{k=i-lowbit(i)+1}^ival_k\)

考虑强行令树状数组的大小为\(2\)的幂

每次二分边界\([l,r]\),中点就是\(mid\)

然后就会发现,因为树状数组大小是\(2\)的幂,每次二分其实就相当于在判断二进制下某一位是否能填作\(1\)

因此,\(a_{mid}\)的值实际上就等于\(\sum_{k=l}^{mid}val_k\)

唔姆,假设树状数组的\(val_i\)表示等于\(i\)的数的个数,那么\(a_{mid}\)的值等同于值在\([l,mid]\)范围内的数的个数。

于是就能轻松实现第\(k\)大的询问了。

模板:普通平衡树

其实只要能够求第\(k\)大,平衡树的其他操作都非常简单了,前驱/后继都可以转化为询问排名再询问第\(k\)大的操作。

唯一一个特殊注意点,就是由于这道题中值可能为负,需要把每个数都加上\(10^7\)再操作。

代码\(class\)封装,码风毒瘤(尽管在这道题中体现不多)。

/*友情提醒:I表示inline,可忽略;RI表示register int,CI表示const int&,可以直接视作int;W表示while*/
class TreeArray//树状数组实现平衡树 
{
	private:
		#define V (1<<25)//V存储总值域,必须为2的幂 
		#define P 10000000//因为存在负数,所有数据都要加上P
		int a[V+5];I void U(RI x,CI v) {W(x<=V) a[x]+=v,x+=x&-x;}//后缀修改 
		I int Q(RI x,RI t=0) {W(x) t+=a[x],x-=x&-x;return t;}//求前缀和(小于等于x的数的个数) 
	public:
		I int Kth(RI k) {RI l=1,r=V,mid;W(l^r) a[mid=l+r>>1]<k?(k-=a[mid],l=mid+1):r=mid;return l-P;}//树状数组上二分询问第k大
		I void Add(CI x) {U(x+P,1);}I void Del(CI x) {U(x+P,-1);}I int Rk(CI x) {return Q(x+P-1)+1;}//插入;删除;询问排名 
		I int Pre(CI x) {return Kth(Q(x+P-1));}I int Nxt(CI x) {return Kth(Q(x+P)+1);}//前驱;后继 
};

[省选联考 2020 A/B 卷] 冰火战士

  • 有两个数组\(Ice_i,Fire_i\)
  • 每次操作修改某个数组中某个位置上的数。(始终非负)
  • 每次操作完求一个最大的整数\(k\),使得\(\min\{\sum_{i\le k}Ice_i,\sum_{i\ge k}Fire_i\}\)最大,并求出这个最大值。
  • 操作数量\(\le2\times 10^6\)

很显然,\(f(k)=\sum_{i\le k}Ice_i\)\(k\)增大递增,\(g(k)=\sum_{i\ge k}Fire_i\)\(k\)增大递减。

那么根据初中函数知识就可以知道,要使最小值最大,就是求两个函数的交点。

题目很简单,可惜这道题居然卡线段树!

这时候就要请出树状数组上二分啦!

每次先二分出最大的\(k\)满足\(f(k)<g(k)\),由于\(k\)是整数因此不一定能找到交点,还要比较\(f(k)\)\(g(k+1)\)谁更大。

求出最大值之后,还要再次树状数组上二分求出最大的\(k\)

大致思路差不多就这样啦。

\(Postscript\)

树状数组最大的优点就是代码短、常数小,因此真的真的是个非常美丽的算法哦!

posted @ 2020-07-30 17:56  童女讴歌的荣华帝政  阅读(756)  评论(0编辑  收藏  举报