樹狀陣列——在高階字首和上的應用
阿新 • • 發佈:2021-08-04
HDU中超第五場打完後補題,被一個高階字首和折磨了兩天
記錄一下想法
HDU中超第五場打完後補題,被一個高階字首和折磨了兩天
記錄一下想法
樹狀陣列——高階字首和
首先強化一下概念:樹狀陣列的基本(根本)操作是單點修改,單點查詢字首和
通過對字首和做小學減法我們做到了區間查詢
現在要求對 \(a\) 陣列做單點修改,查詢 \(a\) 陣列的二維字首和
設 \(b,c\) 兩陣列分別為一階、二階字首和
\[\begin{align} b_i &= \sum_1^i a_i,\quad c_i = \sum_1^ib_i\\ c_i &= ia_1 + (i-1)a_2+...+2a_{i-1} + a_i\\ &= i(a_1+a_2+...+a_i) - [(i-1)a_i + (i-2)a_{i-1}+....+a2+0] \end{align} \]這樣就將一個二階字首和問題轉化成了兩個一階字首和問題
用兩個樹狀陣列,分別處理 \(a_i\) ,\({(i-1)a_i}\) 這兩個序列即可
struct BitTree{//4行樹狀陣列 ll t[2*maxn]; ll lb(int x) {return x&-x;} void add(int p, ll ad){for(int i=p;i<=n;i+=lb(i)) t[i]+=ad;} ll sum(int p){ return p?t[p] + sum(p-lb(p)):0;} }; struct BitTree2{//二階字首和的樹狀陣列 BitTree bt1, bt2; void add(int p, ll ad){ bt1.add(p, ad); bt2.add(p, ad * (p-1)); } ll sum(int p){ return bt1.sum(p) * p - bt2.sum(p); } };
練習(模板題)
線段樹最基礎的模板題,區間修改區間查詢,可以用二階字首和的樹狀陣列做
具體而言,建立差分陣列 \(d\) ,區間修改可以改為在 \(d\) 上的單點修改,那麼區間查詢就相當於查詢 \(d\) 的二階字首和
BitTree2 bt; ll d[maxn], n, m; void solve(){ cin >> n >> m; for(int i=1;i<=n;i++){ cin >> d[i]; bt.add(i, d[i]); bt.add(i+1, -d[i]); } for(int i=1;i<=m;i++){ int opt,x,y,k; cin >> opt >> x >> y; if(opt == 1){ cin >> k; bt.add(x, k); bt.add(y+1, -k); }else{ cout << bt.sum(y) - bt.sum(x-1) << '\n'; } } }
更高階呢?
以三階字首和為例
\[\begin{align} b_i &= \sum_1^i a_i, \quad c_i = \sum_1^ib_i, \quad d_i = \sum_1^ic_i \\ d_k &= \frac{k(1+k)}{2}a_1 + \frac{(k-1)k}{2}a_2+...+\frac{(2+k-i)*(1+k-i)}{2}a_i+...+\frac{3}{2}a_{k-1} + a_k \\ &= \frac{(k+1)(k+2)}{2}(a_1+...a_k)-\frac{2k+3}{2}(a_1+2a_2+...+ka_k)+\frac{1}{2}(a_1+4a_2+...+k^2a_k) \end{align} \]推方程的時候,對於一般項 \(\frac{(2+k-i)*(1+k-i)}{2}a_i\),要注意把 \(k\) 和 \(i\) 分開,目的是使某個\(f(i)a_i\) 形式的東西獨立出來,方便單獨用樹狀陣列維護。
這裡的過程是:
\[\frac{(2+k-i)*(1+k-i)}{2}a_i = \frac{(k+1)(k+2)}{2}a_i-\frac{2k+3}{2}ia_i+\frac{1}{2}i^2a_i \]由此也成功轉化成三個一階字首和問題
如果繼續推下去,會發現 \(n\) 個樹狀陣列就可以解決 \(n\) 階字首和問題
練習題(比賽原題)
三階字首和
題解見自己寫的 2021暑假 HDU中超 第五場 1009