寫在前面
有的時候,我們會發現對於一個序列,它的值域很大,我們算法的復雜度又是 \(\Theta (\mbox{值域})\) 的,但同時我們發現,我們只關心序列中元素的大小關系,卻不關心數到底是多少,比如說我們要找到序列中最大元素的位置,在這個問題下,序列 1 2 3 4 5
和序列 6 10 12 18 34
是等價的,這個時候我們可以通過一些方法將后一個序列映射到前一個序列中去,使得序列的值域變小,這個過程就是我們常說的離散化。
下面給出兩種離散化的方式。
為了表述方便,用 arr
表示原序列,用 res
表示離散化后的序列,同時規定數組下標從 \(1\) 開始。
方法一
先想一下,我們要離散化的話,只要將一個數的大小排序 rank
和一個數的原位置 place
記錄下來即可。獲得一個數的 rank
可以通過排序完成,同時我們為了保留原來的位置信息就考慮用結構體來保存,在排序之后我們便只需要按順序來訪問 arr
數組,就可以同時得到原序列中每一個數的大小關系和位置了,這個時候我們只需要將它們一個個放回到 res
中就行了。
int res[100];
struct node {
int num, place;
node(int nn = 0, int pp = 0) {
num = nn;
place = pp;
}
}arr[100];
bool CMP(const node &a, const node &b) {
return a.num < b.num;
}
int main() {
//input
std::sort(arr + 1, arr + 100 + 1, CMP);
for (int i = 1; i < 100; ++i) {
res[arr[i].place] = i;
}
}
注意到上述方法並沒有去重,事實上如果要去除重復元素的話,只要把第十九行的后一個 i 改成一個計數器即可。
方法二
方法一相對來講是比較麻煩的,定義了很多變量,我們可不可以少用一些變量呢?幸運的是, STL 為我們提供了這樣的便利。
先來認識兩個函數:
unique(start, end);
lower_bound(start, end, key);
unique
函數的作用是去除一個有序序列中的重復元素(保留一個),其作用范圍為左閉右開區間 [start, end) ,並返回去重后的序列的最后一個元素的位置。
lower_bound
函數的作用是在左閉右開區間 [start, end) 中尋找第一個大於等於 key
的數,並返回這個數的位置。
如果我們對排完序的序列后使用 lower_bound
,那么我們得到的就是數的大小關系,所以我們可以直接這樣子寫。
//input
std::sort(arr + 1, arr + n + 1);
cnt = std::unique(arr +1, arr + n + 1) - (arr + 1);
for (int i = 1; i <= n; ++i) {
res[i] = std::lower_bound(arr + 1, arr + cnt + 1, cp[i]) - arr;
}
是不是簡潔了很多,但是在這種情況下,我們就需要對原數組進行一次備份(cp
數組),以便后面的復原。
總結
離散化雖然簡單,但是是一種需要掌握的重要技巧,放一道例題:[NOI2015]程序自動分析;
還有自己做的一道題:Sorting Trees。
有錯誤的地方歡迎更正。