好東西,以后可以不打線段樹了
本篇假定讀者都會最基礎的兩種樹狀數組,即區改單查和單改區查
思考如何維護一個區間的值,想到了差分
對一個差分數組做一次前綴和可以得到每個位置的值
再對每個位置累加一下就是一個區間的值
公式化的講,就是
設差分數組為\(c\)
則每個位置的值
\(val_i=\sum\limits_{j=1}^ic_j\)
一個區間\([l,r]\)的值
\(s_{l,r}=\sum\limits_{i=l}^rval_i\)
寫成前綴和相減的形式就是
\(s_{l,r}=\sum\limits_{i=1}^rval_i-\sum\limits_{i=1}^{l-1}val_i\)
不難發現,一個區間的值實際上就是差分數組前綴和的前綴和做減法
也就是說,我們只要維護出前綴和的前綴和就可以用樹狀數組維護區間了
考慮如何維護前綴和的前綴和
\(s_p=\sum\limits_{i=1}^p\sum\limits_{j=1}^ic_j\)
考慮每個\(c_j\)的出現次數,可以得到
\(s_p=\sum\limits_{i=1}^p\left(p-i+1\right)c_i=\left(p+1\right)\sum\limits_{i=1}^pc_i-\sum\limits_{i=1}^pi *c_i\)
經過如上的簡單推導,我們只要維護\(\sum\limits_{i=1}^pc_i\)和\(\sum\limits_{i=1}^pi *c_i\)這兩個東西就可以了
前者就是差分數組,而后者我們只要在維護差分數組時乘以相應的位置的下標即可
這兩個東西我們都可以用樹狀數組維護單點修改區間查詢一樣的方法維護
int c1[maxn],c2[maxn];
int lbt (int x){ return x & -x; }
void modify (int l,int r,int v)//維護差分數組
{
++r;
for (int i=l;i<=n;i+=lbt(i)) c1[i]+=v,c2[i]+=l*v;
for (int i=r;i<=n;i+=lbt(i)) c1[i]-=v,c2[i]-=r*v;
}
最開始這兩句我沒有看懂,不是說好的維護\(i*c_i\)嗎,怎么寫出來時就變成\(l*v\)和\(r*v\)了
如果你也有這樣的疑惑,這說明太久沒打樹狀數組或者沒想樹狀數組的原理你也忘記樹狀數組的原理了
這個東西維護的是差分數組,而不是差分數組的前綴和!
對於查詢
int query (int l,int r)
{
int res=0;
for (int i=r;i;i-=lbt(i)) res+=(r+1)*c1[i]-c2[i];
for (int i=l-1;i;i-=lbt(i)) res-=l*c1[i]-c2[i];
return res;
}
如有哪里講得不是很明白或是有錯誤,歡迎指正
如您喜歡的話不妨點個贊收藏一下吧