一維差分和二維差分
1、什麼是一維差分?
一維差分是一維字首和的逆運算,一會再展開解釋。
2、一維差分有什麼用?
比如有一大堆數字,比如從1
到10000
,然後給出多組(M
)資料,每次三個引數,開始位置l
、結束位置r
、增加(減少的就是負的)的值
。就是想讓我們每次把這一段數字統一增加一個數,或者統一減少一個數。
樸素辦法:
for(i=l;i<=r;i++) a[i]+=2; //比如每個都加2
就是操作一次,需要r-l
次操作,當然,每次的r
和l
是不一樣的值。
不管具體是多少吧,按演算法複雜度的說法,統統記為n
吧,就是M*n
次操作。
能優化嗎?用差分可以!
3、一維差分的公式推導
一個原始陣列,我們可以構建一個原始陣列對應的差分陣列,差分陣列的字首和就是原陣列。
這句話太繞了,耐心往下看,有個例子就不那麼抽象了~
a[N]
:原始陣列
b[N]
:差分陣列,那麼根據定義:
\(a[1]=b[1]\)
\(a[2]=b[1]+b[2]\)
\(a[3]=b[1]+b[2]+b[3]\)
...
$a[n-1] =b[1]+b[2]+...+b[n-1] $ ①
$a[n] \ \ \ \ \ \ \ =b[1]+b[2]+...+b[n-1]+b[n] $ ②
②- ① 得到
\(a[n]-a[n-1]=b[n]\)
如果我們可以構建出差分陣列,那麼構建需要n
次操作。然後多組(M
)資料,每次需要計算兩次,就是b[l]+=c,b[r+1]-=c
;
就是\(2 * M\)次,整體就是$ n+2 * M$次,複雜度明顯降低。
舉個栗子秒懂:
原始陣列:1 2 3 4 4 5 6 6 7 5
讓我們執行三次操作:
(1) 從l=2
開始到r=5
結束,增加2。
(2) 從l=4
開始到r=6
結束,減少1。
(3) 從l=1
開始到r=3
結束,增加3。
樸素辦法:
(1) for i=2;i<=5;i++ {a[i]+=2}
執行了4次
(2) for i=4;i<=6;i++ {a[i]-=1}
執行了3次
(3) for i=1;i<=3;i++ {a[i]+=3}
執行了3次
所以總次數就是4+3+3=10
次。
我們來嘗試構建一下差分陣列:
原始陣列s:1 2 3 4 4 5 6 6 7 5
差分陣列b: 1 1 1 1 0 1 1 0 1 -2
那麼
b[i]=s[i]-s[i-1]
s[i]=b[1]+b[2]+...+b[i]
4、差分陣列用途
b[l]+=c
b[r+1]-=c
其實也就是上面的那個例子描述的資訊。差分不用思考如何構建,就是按定義,把原陣列從頭到尾一個個計算就完事了,注意所謂一個個,就是l=r的意思。
5、差分陣列與原陣列一樣長嗎?為什麼我看到了 b[r+1]=c,這樣差分不就多了一個元素嗎?
不是一樣長的,差分陣列其實是比原陣列多一個元素的,多的那個在陣列的最後面。
當然,一般我們是不用到最後面的那個元素的,因為,差分的概念都是描述當前位置和前一個位置的差,字首和就是原陣列了,最後一個生成的元素可以視為無效元素,沒有必要再糾結了。
C++ 程式碼
#include<iostream>
using namespace std;
const int N=100010;
int n,m;
int a[N],b[N];
void insert(int l,int r,int c){
b[l]+=c;
b[r+1]-=c;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&a[i]),insert(i,i,a[i]);
while(m--){
int l,r,c;
scanf("%d%d%d",&l,&r,&c);
insert(l,r,c);
}
for(int i=1;i<=n;i++)b[i]+=b[i-1],printf("%d ",b[i]);
return 0;
}