區間最值操作 & 區間歷史最值
本文講解吉老師在 2016 年國家集訓隊論文中提到的線段樹處理歷史區間最值的問題。
區間最值
籠統地説,區間最值操作指,將區間 \([l,r]\) 的數全部對 \(x\) 取 \(\max\) 或 \(\min\),即 \(a_i=\max(a_i,x)\) 或者 \(a_i=\min(a_i,x)\)。給一道例題吧。
HDU5306 Gorgeous Sequence
維護一個序列 \(a\):
0 l r t\(\forall l\le i\le r,\ a_i=\min(a_i,t)\)。1 l r輸出區間 \([l,r]\) 中的最大值。2 l r輸出區間和。多組數據,\(T\le 100,n\le 10^6,\sum m\le 10^6\)。
區間取 min,意味着只對那些大於 \(t\) 的數有更改。因此這個操作的對象不再是整個區間,而是「這個區間中大於 \(t\) 的數」。於是我們可以有這樣的思路:每個結點維護該區間的最大值 \(Max\)、次大值 \(Se\)、區間和 \(Sum\) 以及最大值的個數 \(Cnt\)。接下來我們考慮區間對 \(t\) 取 \(\min\) 的操作。
- 如果 \(Max\le t\),顯然這個 \(t\) 是沒有意義的,直接返回;
- 如果 \(Se<t\le Max\),那麼這個 \(t\) 就能更新當前區間中的最大值。於是我們讓區間和加上 \(Cnt(t-Max)\),然後更新 \(Max\) 為 \(t\),並打一個標記。
- 如果 \(t\le Se\),那麼這時你發現你不知道有多少個數涉及到更新的問題。於是我們的策略就是,暴力遞歸向下操作。然後上傳信息。
這個算法的複雜度如何?使用勢能分析法可以得到複雜度是 \(O(m\log n)\) 的。具體分析過程見論文。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | |
BZOJ4695 最假女選手
長度為 \(n\) 的序列,支持區間加 \(x\)/區間對 \(x\) 取 \(\max\)/區間對 \(x\) 取 \(\min\)/求區間和/求區間最大值/求區間最小值。
\(N,M\le 5\times 10^5,|A_i|\le 10^8\)。
同樣的方法,我們維護最大、次大、最大個數、最小、次小、最小個數、區間和。除了這些信息,我們還需要維護區間 \(\max\)、區間 \(\min\)、區間加的標記。相比上一道題,這就涉及到標記下傳的順序問題了。我們採用這樣的策略:
- 我們認為區間加的標記是最優先的,其餘兩種標記地位平等。
- 對一個結點加上一個 \(v\) 標記,除了用 \(v\) 更新衞星信息和當前結點的區間加標記外,我們用這個 v 更新區間 \(\max\) 和區間 \(\min\) 的標記。
- 對一個結點取 \(v\) 的 \(\min\)(這裏忽略暴搜的過程,假定標記滿足添加的條件),除了更新衞星信息,我們要與區間 \(\max\) 的標記做比較。如果 \(v\) 小於區間 \(\max\) 的標記,則所有的數最後都會變成 v,那麼把區間 \(\max\) 的標記也變成 \(v\)。否則不管。
- 區間取 v 的 \(\max\) 同理。
另外,BZOJ 這道題卡常……多數組線段樹的常數比結構體線段樹的常數大……在維護信息的時侯,當只有一兩個數的時侯可能發生數集重合,比如一個數既是最大值又是次小值。這種要特判。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 | |
吉老師證出來這個算法的複雜度是 \(O(m\log^2 n)\) 的。
Mzl loves segment tree
兩個序列 \(A,B\),一開始 \(B\) 中的數都是 \(0\)。維護的操作是:
- 對 \(A\) 做區間取 \(\min\)
- 對 \(A\) 做區間取 \(\max\)
- 對 \(A\) 做區間加
- 詢問 \(B\) 的區間和
每次操作完後,如果 \(A_i\) 的值發生變化,就給 \(B_i\) 加 \(1\)。\(n,m\le 3\times 10^5\)。
先考慮最容易的區間加操作。只要 \(x\neq 0\) 那麼整個區間的數都變化,所以給 B 作一次區間加即可。
對於區間取最值的操作,你發現你打標記與下傳標記是與 \(B\) 數組一一對應的。本質上你將序列的數分成三類:最大值、最小值、非最值。並分別維護(只不過你沒有建出具體的最值集合而已,但這並不妨礙維護的操作)。因此在打標記的時侯順便給 \(B\) 更新信息即可(注意不是給 \(B\) 打標記!是更新信息!)。查詢的時侯,你在 \(A\) 上查詢,下傳標記的時侯順便給 \(B\) 更新信息。找到需要的結點後,返回 \(B\) 的信息即可。這種操作本質上就是把最值的信息拿給 \(B\) 去維護了。另外仍要處理數集的重複問題。
CTSN loves segment tree
兩個序列 \(A,B\):
- 對 \(A\) 做區間取 \(\min\)
- 對 \(B\) 做區間取 \(\min\)
- 對 \(A\) 做區間加
- 對 \(B\) 做區間加
詢問區間的 \(A_i+B_i\) 的最大值
\(n,m\le 3\times 10^5\)。
我們把區間中的 位置 分成四類:在 \(A,B\) 中同是區間最大值的位置、在 \(A\) 中是區間最大值在 \(B\) 中不是的位置、在 \(B\) 中是區間最大值在 \(A\) 中不是的位置、在 \(A,B\) 中都不是區間最大值的位置。對這四類數分別維護 答案 和 標記 即可。舉個例子,我們維護 \(C_{1\sim 4},M_{1\sim 4},A_{\max},B_{\max}\) 分別表示當前區間中四類數的個數,四類數的答案的最大值,\(A\) 序列的最大值、\(B\) 序列的最大值。然後合併信息該怎麼合併就怎麼合併了。
複雜度仍是 \(O(m\log^2 n)\)。
小結
在第本章節中我們給出了四道例題,分別講解了基本區間最值操作的維護、多個標記的優先級處理、數集分類的思想以及多個分類的維護。本質上處理區間最值的基本思想就是數集信息的分類維護與高效合併。在下一章節中,我們將探討歷史區間最值的相關問題。
歷史最值問題
歷史最值不等於可持久化
注意,本章所講到的歷史最值問題不同於所謂的可持久化數據結構。這類特殊的問題我們將其稱為歷史最值問題。歷史最值的問題可以分為三類。
歷史最大值
簡單地説,一個位置的歷史最大值就是當前位置下曾經出現過的數的最大值。形式化地定義,我們定義一個輔助數組 \(B\),一開始與 \(A\) 完全相同。在 \(A\) 的每次操作後,我們對整個數組取 \(\max\):
這時,我們將 \(B_i\) 稱作這個位置的歷史最大值,
歷史最小值
定義與歷史最大值類似,在 \(A\) 的每次操作後,我們對整個數組取 \(\min\)。這時,我們將 \(B_i\) 稱作這個位置的歷史最小值,
歷史版本和
輔助數組 \(B\) 一開始全部是 \(0\)。在每一次操作後,我們把整個 \(A\) 數組累加到 \(B\) 數組上
我們稱 \(B_i\) 為 \(i\) 這個位置上的歷史版本和。
接下來,我們將歷史最值問題分成四類討論。
可以用標記處理的問題
CPU 監控
序列 \(A,B\) 一開始相同:
- 對 \(A\) 做區間覆蓋 \(x\)
- 對 \(A\) 做區間加 \(x\)
- 詢問 \(A\) 的區間 \(\max\)
- 詢問 \(B\) 的區間 \(\max\)
每次操作後,我們都進行一次更新,\(\forall i\in [1,n],\ B_i=\max(B_i,A_i)\)。\(n,m\le 10^5\)。
我們先不考慮操作 1。那麼只有區間加的操作,我們維護標記 \(Add\) 表示當前區間增加的值,這個標記可以解決區間 \(\max\) 的問題。接下來考慮歷史區間 \(\max\)。我們定義標記 \(Pre\),該標記的含義是:在該標記的生存週期內,\(Add\) 標記的歷史最大值。
這個定義可能比較模糊。因此我們先解釋一下標記的生存週期。一個標記會經歷這樣的過程:
- 在結點 \(u\) 被建立。
- 在結點 \(u\) 接受若干個新的標記的同時,與新的標記合併(指同類標記)
- 結點 \(u\) 的標記下傳給 \(u\) 的兒子,\(u\) 的標記清空
我們認為在這個過程中,從 1 開始到 3 之前,都是結點 \(u\) 的標記的生存週期。兩個標記合併後,成為同一個標記,那麼他們的生存週期也會合並(即取建立時間較早的那個做為生存週期的開始)。一個與之等價的説法是,從上次把這個結點的標記下傳的時刻到當前時刻這一時間段。
為什麼要定義生存週期?利用這個概念,我們可以證明:在一個結點標記的生存週期內,其子結點均不會發生任何變化,並保留在這個生存週期之前的狀態。道理很簡單,因為在這個期間你是沒有下傳標記的。
於是,你就可以保證,在當前標記生存週期內的歷史 \(Add\) 的最大值是可以更新到子結點的標記和信息上的。因為子結點的標記和信息在這個時間段內都沒有變過。於是我們把 \(u\) 的標記下傳給它的兒子 \(s\),不難發現
那麼信息的更新也是類似的,拿對應的標記更新即可。
接下來,我們考慮操作 1。
區間覆蓋操作,會把所有的數變成一個數。在這之後,無論是區間加減還是覆蓋,整個區間的數仍是同一個(除非你結束當前標記的生存週期,下傳標記)。因此我們可以把第一次區間覆蓋後的所有標記都看成區間覆蓋標記。也就是説一個標記的生存週期被大致分成兩個階段:
- 若干個加減操作標記的合併,沒有接收過覆蓋標記。
- 覆蓋操作的標記,沒有所謂的加減標記(加減標記轉化為覆蓋標記)
於是我們把這個結點的 Pre 標記拆成 \((P_1,P_2)\)。\(P_1\) 表示第一階段的最大加減標記;\(P_2\) 表示第二階段的最大覆蓋標記。利用相似的方法,我們可以對這個做標記下傳和信息更新。時間複雜度是 \(O(m\log n)\) 的(這個問題並沒有區間對 \(x\) 取最值的操作哦~)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | |
本页面最近更新:,更新历史
发现错误?想一起完善? 在 GitHub 上编辑此页!
本页面贡献者:OI-wiki
本页面的全部内容在 CC BY-SA 4.0 和 SATA 协议之条款下提供,附加条款亦可能应用