問題
,範圍最小值問題。具體表現爲一下一類問題:
給出一個 個元素的數組 ,求解 : 計算 { }
問題有很多解法,其中較爲快捷和簡便的是 的 算法,簡稱 表。
算法基於倍增思想,整個算法由預處理和查詢兩部分組成,分別描述一下:
預處理
我們令 爲從 開始的, 長度爲 的一段元素中的最小值。根據倍增思想, 可以通過 和 轉移得到,具體操作就是對兩個數取 。
沒有接觸過倍增思想的同學可能對這步表示有點難以理解,具體解釋一下:
表示的是從 開始的長度爲 的一段元素中的最小值,區間右端點是 。
表示的是從 開始的長度爲 的一段元素中的最小值,區間右端點是 。
表示的是從 開始的長度爲 的一段元素中的最小值,區間右端點是 。
現在就明顯了, 這段區間被劃分成了了 和 兩段區間,不重不漏,所以這樣操作是可行的。
預處理的時間複雜度是 。
查詢
有了剛纔對預處理的講述,查詢部分應該不難想到,我們令 爲滿足 的最大整數。則可知 。則以 開頭, 以 結尾的兩個長度爲 的區間合併起來就覆蓋了 。由於是求範圍最小值,有元素被重複計算也沒問題。
則 。
查詢的時間複雜度是 。
由此可見, 算法思想簡單,好寫,是求解 問題的首選算法。
具體實現的時候還要注意一點,每次用 計算 是非常浪費時間的。由於計算機內部使用的是二進制,我們可以用 表示 。
namespace RMQ {
void init(int n) {
for (int i = 1; i <= n; i++) d[i][0] = a[i];
for (int j = 1; (1 << j) <= n; j++)
for (int i = 1; i + (1 << j) - 1 <= n; i++)
d[i][j] = min(d[i][j - 1], d[i + (1 << (j - 1))][j - 1]);
}
int query(int l, int r) {
int k = log2(r - l + 1);
return min(d[l][k], d[r - (1 << k) + 1][k]);
}
}