1. 程式人生 > 其它 >一維差分和二維差分

一維差分和二維差分

1、什麼是一維差分?

一維差分是一維字首和的逆運算,一會再展開解釋。

2、一維差分有什麼用?

比如有一大堆數字,比如從110000,然後給出多組(M)資料,每次三個引數,開始位置l結束位置r增加(減少的就是負的)的值。就是想讓我們每次把這一段數字統一增加一個數,或者統一減少一個數。

樸素辦法:

  for(i=l;i<=r;i++)  a[i]+=2; //比如每個都加2  

就是操作一次,需要r-l次操作,當然,每次的rl是不一樣的值。
不管具體是多少吧,按演算法複雜度的說法,統統記為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;
}