108 師大附中校隊培訓
很多題目和「區間」有關,區間指的是序列中一段連續的部分,而很多題目會同時出現修改和查詢兩種操作,例如:
給一個長度為 的數列,接下來有 筆詢問,詢問有兩種類型:
- 將 這個區間的數字全部加上 。
- 輸出 這個區間的最大值。
輸入第一行有兩個數字 和 ,接下來有 行,每行表示一筆詢問,第一個數字是 ,表示詢問的類型:
- 如果 ,接下來會有三個數字 、 和 ,表示要將 這個區間中所有數字加上 。 如果 ,接下來會有兩個數字 、,請輸出目前區間 中的最大值。
一個序列 的區間 指的是 。
這是很經典的區間最大值問題,可以看到題目會要求你做兩種事,而且它是一下子要修改、一下子要查詢,如果暴力修改、暴力查詢的話,修改和查詢時間都是 ,乘上詢問筆數就會變成 ,看起來還好?
這其實很糟,很多題目 都 以上,這個複雜度顯然不夠。接下來介紹的東西都會是用來處理這種關於區間詢問的問題。
區間問題會有很多種,區間問題的操作(詢問)大致上就分為修改和查詢兩種。
修改可能是改數字、把數字加上某個值、把數字開根號……等等,也可能一題裡面有多種修改,修改也可能會很複雜,像是把區間裡的第偶數項 、奇數項 之類的。
查詢也很多種,像是數字的和、最大的數字……等等。
修改或查詢的範圍也有可能不是一個區間,而是單一個位置,這樣稱為單點修改和單點查詢,否則,就稱為區間修改和區間查詢。既然是「區間」問題,那查詢和修改中其中一個當然要是區間的,而另一個可以是區間也可以是單點,不同範圍類型的問題,可以套不同的作法,例如:
當然根據題目不同,可以選擇不一樣的作法,哪個類型要用什麼作法也沒有固定,且除了範圍差異之外,不同題目也有查詢的東西、修改的方式的差異,都會影響解法。
前綴和是常用來處理區間問題的一個手法,也就是求出數列的每一個前綴所有數字的和。
長度 的數列 ,令 ,那麼 就是 的前綴和序列,每一個 都是 的一個前綴和。
給一個長度為 的數列 ,有 筆詢問,每筆詢問求區間 所有數字的和。
可以看到這題只有查詢區間和,不過如果暴力做,複雜度會變成 。如果用前綴和去想,會發現:
所以只要 把所有前綴和算出來,接下來每次查詢都只要 就可以得到區間和了,複雜度變成了 。
給一個長度 的數列 ,有 次修改,每次修改將 範圍中所有數字都加上 ,接著有 次查詢,請輸出最後 為多少。
雖然這個問題是做完全部修改,再做查詢,讓這題沒有像剛剛那題複雜,但是如果在區間修改的時候硬做,複雜度會變成 ,顯然太慢了。
令 、,也就是 是 與它前一項的差,如果要把 都加上 ,那麼 應該會多 ,而 會少 ,所以只要把 加上 、 減去 就好(當然如果 就不用理它),這樣區間修改就變成單點修改了。
至於要怎麼推回 呢?注意到
只要算出前綴和就可以得到 了,這麼做一遍的話是 ,如果是全部修改完再做查詢,就只要 算一次 ,再 查詢就可以了,但是如果是要一下修改、一下查詢的話,每次修改完都要再重新花 算一次 ,這樣太久了,之後會介紹到一個叫樹狀數組(BIT)的資料結構來解決這個問題。
稱為 的差分序列,這是常用於區間問題的手法之一。
有的時候,需要計算一些數字出現了幾次,例如:
在這裡面, 出現了 次、 出現 次……,如果開一個 map 來存每個數字出現了幾次,這樣不方便做區間問題(例如大小介於 的數字出現了幾次),但如果用 vector、陣列之類的來存,也有可能出現很大數字,導致記憶體開不下。
此時就需要用到離散化,把數列中第一小的數字換成 ,第二小換成 ……依此類推,如果你想要的話,也可以第一大的換成 。如果需要用到原始值做運算,就把原始值和新值的對照表存下來就好。
前綴和和差分兩者都可以在 的預處理後, 做到區間操作。差分可以做到 區間修改,前綴和可以做到 區間和查詢,不過差分不能同時做查詢、前綴和不能同時做修改,而且前綴和只能算和,無法算最大、最小值,所以除了它們之外,還需要更高級的東西來幫助區間操作。