【樹狀陣列·進階篇】樹狀陣列實現平衡樹(樹狀陣列上二分)
\(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\)
樹狀陣列最大的優點就是程式碼短、常數小,因此真的真的是個非常美麗的演算法哦!