【差分】簡單差分的思想與應用介紹

一、差分介紹與代碼模板

如果我們需要維護的數據是“兩個相鄰數據之差”,這就是差分。令di=aiai1d_{i}=a_{i}-a_{i-1},則稱ddaa的差分數組。也可以寫成Δai=aiai1\Delta a_{i} = a_{i} - a_{i-1}

差分的寫法與前綴和專欄的那篇介紹文章差不多,我們使用的數據數組aa和差分數組dd都是從1開始:

【差分模板】:

void getDiff(int a[], int n) { //a[0]=0, 使用[1,n]
	for (int i = 1; i <= n; ++i) 
		d[i] = a[i] - a[i - 1]; //d[0]=0 
}

二、從差分到原數組

其實從前綴和也可以很輕易的求出原數組,不過是求區間和的區間的左右兩端點相等罷了(即a[i]=sum[i]sum[i1]a[i] = sum[i] - sum[i - 1])。

同樣的思想,我們也可以拿着差分數組dd推出aa數組。而且a[i]=a[i1]+d[i]a[i] = a[i - 1] + d[i]aia_{i}相當於是差分數組dd的前綴和sum[i]sum[i] … 不說相當於,其實,aa數組就是dd數組的前綴和數組

如果要求aa數組的區間和,怎麼做呢?(^_^)

所以,從我們約定的前綴和的寫法(方便遞推求前綴和數組)和求差分這兩種角度來看,就寫a[0]=0a[0]=0

簡單的舉例:

a   : 0 | 3 7  5 8 (a[0]默認爲0)
d   : 0 | 3 4 -2 3
sum : 0 | 3 7  5 8

很容易想明白,現在我在0樓,上了3樓,又上了4層樓,再往下走了兩層樓,然後又上了三層樓,那麼我現在位於0+3+4-2+3=8樓。

代碼如下:

void getA(int p[], int n) { //d[0]=0, 使用[1,n]
	for (int i = 1; i <= n; ++i) 
		a[i] = a[i - 1] + d[i];
}

前綴和可以讓人以O(1)的時間求出一個區間的和。差分的用途也差不多,用來優化算法,甚至可以優化掉一個次方的複雜度。

下面具體介紹其應用。

三、差分的應用【區間加】

給定一個序列aa,其初值全爲0,有很多(m)次操作,允許離線,形如:AlrkA \quad l \quad r \quad k,將a[l,r]a_{[l,r]}中的每個值加上kk。爲方便,這裏的l1,r1l \geq 1, r \geq 1。最後輸出整個數組,複雜度要求O(n)O(n)

舉例如下:

a : 0 | 0 0 0 0 0 0

A 1 2 3
-> a : 0 | 3 3 0 0 0 0

A 2 5 -2
-> a : 0 | 3 1 -2 -2 -2 0

A 5 6 3
-> a : 0 | 3 1 -2 -2 1 3

最簡單的想法,就是對原數組進行操作,每次對區間[l,r][l,r]進行相應的操作,時間爲O(mn)O(mn),m很大時甚至會達到平方的複雜度。下面的做法,可以真正做到O(n)O(n)

不難發現,區間[l,r][l,r]加k,實際上發生瞭如下的兩件事:

  • a[l]a[l]比前一個元素多了kk
  • a[r+1]a[r + 1]比前一個元素少了kk;(不用大了小了的說法,會引起誤解)
    在這裏插入圖片描述

在差分數組dd的基礎上面,可以對應轉換爲兩個操作:

  • d[l]+=kd[l] += k
  • d[r+1]=kd[r + 1] -= k

即,一次區間加,只修改這兩個元素,區間內部的數據之間的差值沒有變化,對應於差分數組相應區別不變。

最後利用上面的(求前綴和)方法,從dd數組求出aa數組,即爲答案。

例子:

a : 0 | 0 0 0 0 0 0
d : 0 | 0 0 0 0 0 0

A 1 2 3
-> a : 0 | 3 3  0 0 0 0
-> d : 0 | 3 0 -3 0 0 0

A 2 5 -2
-> a : 0 | 3  1 -2 -2 -2 0
-> d : 0 | 3 -2 -3  0  0 2

A 5 6 3
-> a : 0 | 3  1 -2 -2  1 3
-> d : 0 | 3 -2 -3  0  3 2  (r + 1 > n, 計不計算都行, p數組開大一些)

區間加

void add(int l, int r, int k) {
	d[l] += k;
	d[r + 1] -= k;
}

void getA() {
	for (int i = 1; i <= n; ++i) //預先指定a[0]=0
		a[i] = a[i - 1] + d[i];

如果是單點加,用上面的add完全沒問題:
dx+=k,dx+1=k.d_{x} += k, d_{x+1} -= k.

本質和區間加一模一樣。

四、差分應用拓展【區間加等差+單點查詢】

給定一個序列aa,其初值全爲0,有很多(m)次操作,操作有兩種,強制在線,形如:

  • 區間加等差數列:AlrfwA \quad l \quad r \quad f \quad w,將a[l,r]a_{[l,r]}中的每個元素,加上首項爲ff、公差爲ww的等差數列對應的元素,即a[l]+=f1a_{[l]} += f_{1}a[l+1]+=f2a_{[l + 1]} += f_{2},… 。
  • 單點查詢:xx ,求出axa_{x}

爲方便,這裏的l1,r1l \geq 1, r \geq 1。最後輸出整個數組。

e.g.e.g. 目前a={1,2,3,3,3,3}a=\{1,2,3,3,3,3\},給[3,5][3,5]加上首項爲22、公差爲11的等差數列,aa變爲{1,2,5,6,7,3}\{1,2,5,6,7,3\}

原來的d={1,1,1,0,0,0}d=\{1,1,1,0,0,0\},修改後的dd數組:{1,1,3,1,1,4}\{1,1,3,1,1,-4\}。顯然:aa區間加等差,相當於dd區間加常數、端點單點修改

我們自然要把d[l+1,r]d_{[l + 1,r]}加上wwdld_{l}加上f1f_{1};那麼右端點呢?要把剛纔的操作復原dr+1=f1+(rl)wd_{r+1} -= f_{1} + (r - l)w

因此,我們要解決的問題在於,維護dd數組,支持單點修改、區間加,可以使用線段樹加以解決。

五、總結

這裏介紹了簡單的差分思想,還有區間加二次函數,多次差分,樹上差分,有限微積分…

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章