差分标记算法笔记
差分有:一維差分、多維差分、樹上差分 差分標記一般求離線區間問題!(修改完后不再修改,然后修改結束后查詢)
對于帶有“將一段區間內的每個數全部加上某個值”這種操作的題目,通常考慮差分原數列以簡化情況,將對一段區間的操作轉化為對某兩個特定數的操作。
我們可以用樹狀數組來維護一個差分序列。差分序列的本質是通過前綴和使區間修改轉換為單點修改。所以在查詢的時候只要輸出前綴和就可以了。
首先,給出一個問題:
給出n個數,再給出Q個詢問,每個詢問給出le,ri,x,要求你在le到ri上每一個值都加上x,而只給你O(n)的時間范圍,怎么辦?
思考一下:
如果暴力,卡一下le和ri,隨隨便便讓你O(n^2)T成狗。
用線段樹或樹狀數組搞一搞,抱歉,這個復雜度是O(Q logn)的,還是會T!(雖然他們解決別的題目很NB)
差分,沒錯,就是標題,很高興O(n)+常數......
1.先另外開一個專門差分的數組(大小=題中的序列長度)
2.假如在3~8的區間上加上5,那我們在差分數組中的3位置上加上一個5(原因暫時不懂沒關系,用筆先跟著模擬),再在8+1的位置上減一個5,如此操作完Q次。
3.假如我們只有這一次操作,開始統計答案,運用前置和的思想,cfi=cf[i-1]+cf[i].那么你會發現(如果你模擬了的話),在3~8的區間上,你已經使差分數組全部加上了5(推廣到所有Q一起統計答案依舊正確)
4.再用O(n)的for把他們加到原序列之中去,輸出!
看一下復雜度,果然:O(常數*n).
博客上看拉個題目意思大概是:
給定一個長度為N的序列: 首先進行X次操作,每次操作在Li和Ri這個區間加上一個數Ci。
然后進行Y次詢問,每次詢問Li到Ri的區間和。
初始序列都為0。
1<=N<=1000000,1<=X<=N, X<=Y<=N
1<=Li<=N,Li<=Ri<=N,|Ci|<=100000000000000
很多人第一眼看到這個題目第一反應都是線段樹的裸題?但是本人認為線段樹對于蒟蒻來說在大考中代碼實現復雜,如果寫的不太熟悉的話,運用大量時間去實現其是不夠理智的,不過對于這個題利用差分數組解題是個不錯的選擇。
差分數組(差分數列):
對于一個數組A[ ],其差分數組D[i]=A[i]-A[i-1] (i>0)且D[0]=A[0]
令SumD[i]=D[0]+D[1]+D[2]+…+D[i] (SumD[ ]是差分數組D[ ]的前綴和)
則SumD[i]=A[0]+A[1]-A[0]+A[2]-A[1]+A[3]-A[2]+…+A[i]-A[i-1]=A[i]
即A[i]的差分數組是D[i], 而D[i]的前綴和是A[i]
對于“數列游戲”這題: 如果每次修改都修改從L到R的值的話,一定會TLE。
注意特殊處:這道題是先進行整體區間修改,最后才統一查詢。 所以,我們只要維護一個差分數組就行了。
維護差分數組,對于將區間[L,R]加C,我們只需要將D[L]+C和D[R+1]-C 當修改完畢后,我們先求一遍差分前綴和就得到了修改后的數組A[ ],
然后再對A[ ]求一遍前綴和
這樣每次查詢的時候只要計算一次就可以得到結果了
總的來說差分數組適用于離線的區間修改問題,如果是在線的話應該用線段樹或其他數據結構。
差分數組其實就相當于通過改變區間前端和末端與其他部分的差值,在最后進行累加的時候實行對整個區間的值的改變。
但為什么要存差值呢?————因為數列中的數滿A[i]=sum{D[1]…D[i]},便于用遞推求得最后的值。
---
差分數組是什么呢?
http://www.cnblogs.com/widsom/p/7121047.html
差分數組是前綴和的逆運算,同樣運用到容斥原理
一維:
l<=r
a[l]++;
a[r+1]--;
二維:
x1<=x2&&y1<=y2
a[x1][y1]++;
a[x1][y2+1]--;
a[x2+1][y1]--;
a[x2+1][y2+1]++;
三維:
x1<=x2&&y1<=y2&&z1<=z2
a[x1][y1][z1]++;
a[x2+1][y1][z1]--;
a[x1][y2+1][z1]--;
a[x1][y1][z2+1]--;
a[x1][y2+1][z2+1]++;
a[x2+1][y1][z2+1]++;
a[x2+1][y2+1][z1]++;
a[x2+1][y2+1][z2+1]--;
是不是很簡單,是不是很有規律,相信你能寫出大于3維的情況了
算法筆記--差分標記
所有元素初始值為0才能這么做。
①l--r全加1
a[l]++;
a[r+1]--;
求一遍前綴和為元素本身。
求兩遍前綴和為元素前綴和。
例題1:http://codeforces.com/problemset/problem/816/B
例題2:http://codeforces.com/problemset/problem/834/B
例題3:http://acm.hdu.edu.cn/showproblem.php?pid=1556
②l--r從1加到l-r+1
a[l]++;
a[r+1]-=l-r+2;
a[r+2]+=l-r+1;
求兩遍前綴和為元素本身。
求三遍前綴和為元素前綴和。
因為更新時復雜度是o(1)所以復雜度為求前綴和時的o(N)。
例題:http://arc077.contest.atcoder.jp/tasks/arc077_c
樹上差分(樹的前綴和)
近年的NOIp,似乎對于樹上差分的題目考察越來越熱(參見2015年提高組 運輸計劃,2016年提高組 天天愛跑步)。這些題目都要知道在樹上從某個點到另一個點的所有路徑。但是,暴力求解這種題目經常會TLE。這種題目需要使用樹上差分。在講樹上差分之前,首先需要知道樹的以下兩個性質:
(1)任意兩個節點之間有且只有一條路徑。
(2)一個節點只有一個父親節點
這兩個性質都很容易證明。那么我們知道,如果假設我們要考慮的是從u到v的路徑,u與v的lca是a,那么很明顯,如果路徑中有一點u'已經被訪問了,且u'≠a,那么u'的父親也一定會被訪問,這是根據以上性質可以推出的。所以,我們可以將路徑拆分成兩條鏈,u->a和a->v。那么樹上差分有兩種常見形式:(1)關于邊的差分;(2)關于節點的差分。
①關于邊的差分:
將邊拆成兩條鏈之后,我們便可以像差分一樣來找到路徑了。因為關于邊的差分,a是不在其中的,所以考慮鏈u->a,則就要使cf[u]++,cf[a]--。然后鏈a->v,也是cf[v]++,cf[a]--。所以合起來便是cf[u]++,cf[v]++,cf[a]-=2。然后,從根節點,對于每一個節點x,都有如下的步驟:
(1)枚舉x的所有子節點u
(2)dfs所有子節點u
(3)cf[x]+=cf[u]
那么,為什么能夠保證這樣所有的邊都能夠遍歷到呢?因為我們剛剛已經說了,如果路徑中有一點u'已經被訪問了,且u'≠a,那么u'的父親也一定會被訪問。所以u'被訪問幾次,它的父親也就因為u'被訪問了幾次。所以就能夠找出所有被訪問的邊與訪問的次數了。路徑求交等一系列問題就是通過這個來解決的。因為每個點都只會遍歷一次,所以其時間復雜度為O(n).
②關于點的差分:
還是與和邊的差分一樣,對于所要求的路徑,拆分成兩條鏈。步驟也和上面一樣,但是也有一些不同,因為關于點,u與v的lca是需要包括進去的,所以要把lca包括在某一條鏈中,最后對cf數組的操作便是cf[u]++,cf[v]++,cf[a]--,cf[father[a]]--。其時間復雜度也是一樣的O(n).
通過以上的描述,如果你還是不太能理解,那么以下兩個題目可能可以幫助你理解:
USACO 最大流(樹上差分)https://www.luogu.org/problem/show?pid=3128
NOIp2015 運輸計劃(樹上差分+二分)https://www.luogu.org/problem/show?pid=2680
轉載于:https://www.cnblogs.com/Roni-i/p/9354335.html
總結
- 上一篇: git 与团队协同开发,避免冲掉别人代码
- 下一篇: 洛谷P4145 上帝造题的⑦minute