更新中。
歡迎催更。
Part1 入門雜題
CF1407C Chocolate Bunny
記 \(q(i, j)\) 表示通過一次詢問求出的 \(p_i\bmod p_j\) 的值。
考慮兩個數 \(x, y\),不妨設 \(x < y\),那么:\(x\bmod y = x\),\(y \bmod x < x\)。
也就是說,通過 \((x, y)\), \((y, x)\) 這樣兩次詢問,我們可以獲得的信息是:
- 兩個數的大小關系。
- 較小的那個數的值。
我們依次掃描整個序列。維護一個變量 \(i\) 表示當前前綴最大值所在的位置。初始時 \(i = 1\)。設當前掃描到的位置為 \(i'\)。通過兩次詢問 \(q(i', i)\) 和 \(q(i, i')\):
- 如果 \(q(i', i) > q(i, i')\),說明 \(p_{i'} < p_{i}\),且 \(p_{i'} = q(i', i)\)。
- 如果 \(q(i', i) < q(i, i')\),說明 \(p_{i'} > p_{i}\),且 \(p_{i} = q(i, i')\)。記下 \(p_{i}\) 的值。然后令 \(i\gets i'\)。
全部掃描完成后,最終的 \(i\),就是 \(n\) 所在的位置。且其他位置的值都已經確定。
共需要 \(2n - 2\) 次詢問。
總結:
核心是要想到對兩個數互相詢問。即發現 \(x\bmod y\) 和 \(y\bmod x\) 的關系。
歸類:取模,猜數列。
難度:易。
CF1479A Searching Local Minimum
維護一個區間 \([l,r]\),滿足:
- \([l,r]\) 中至少存在一個位置,是局域最小值(也就是答案)。
- \(a_{l - 1} > a_{l}\)。
- \(a_{r + 1} > a_{r}\)。
初始時,\(l = 1, r = n\)。
如果某個時刻 \(l = r\),說明我們找到了答案。否則設 \(m = \lfloor\frac{l + r}{2}\rfloor\)。詢問出 \(a_{m}\) 和 \(a_{m + 1}\) 的值。因為 \(a\) 是個排列,所以 \(a_{m}\neq a_{m + 1}\)。
- 如果 \(a_{m} < a_{m + 1}\),可以令 \([l, r]\gets[l, m]\)。
- 如果 \(a_{m} > a_{m + 1}\),可以令 \([l, r]\gets [m + 1, r]\)。
因為區間長度每次縮小一半,這相當於一個二分的過程。共需要 \(2\cdot\lceil\log_2(n)\rceil \leq 34\) 次詢問。可以通過本題。
總結:
本題里的這個“二分”非常巧妙。一般的二分,我們都試圖尋找區間里第一個或最后一個滿足某條件的位置,每次我們會判斷中點 \(m\) 相比目標位置是大了還是小了。但在本題里,目標位置並不是唯一的。我們每次判斷的是:左區間/右區間里是否一定存在至少一個目標位置,判斷的條件是中點 \(m\) 是否具有和右端點/左端點相同的性質。
更形象地說,我們有一個 \(01\) 序列 \(x\)。\(x_1 = 0, x_n = 1\)。我們的目標是找到一個位置 \(p\),滿足 \(x_{p} = 0, x_{p + 1} = 1\)。做法是二分,對當前中點 \(m\),若 \(x_{m} = 0\)(中點的性質和左端點一樣),就令 \([l, r]\gets[m, r]\),否則(中點的性質和右端點一樣)令 \([l, r]\gets [l, m]\)。這樣最終一定能找到符合要求的 \(p\)。
這種獨特的二分方法,和微積分上的“區間套”有異曲同工之妙。感興趣的讀者可以自行了解。類似的題目還有:NFLSOJ601 秦始皇。
歸類:二分。
難度:易。
CF1479C Continuous City
本題的答案一定是 \(\texttt{Yes}\)。以下會逐步講解構造方法。
記 \((u, v, w)\) 表示一條從點 \(u\) 連向點 \(v\),邊權為 \(w\) 的邊(\(1\leq u < v\leq 32\))。
子問題一:\(L = 1, R = 2^k\)。
\(k = 0\) 的情況:令 \(n = 2\),只需要連一條邊 \((1, 2, 1)\) 即可。
\(k > 0\) 時,考慮歸納地構造。假設我們已經構造出了一張 \(k + 1\) 個節點的圖,滿足對任意 \(0\leq i < k\),點 \(1\dots i + 2\) 構成的子圖是 \((1, 2^{i})\)-continuous 的。現在考慮添加一個節點 \(k + 2\),在不影響前面節點的前提下,使得整張圖是 \((1, 2^{k})\)-continuous 的。做法如下:
- 首先,從 \(2\dots k + 1\) 中任意點連向點 \(k + 2\) 的路徑,長度必定 \(> 1\)(因為至少含有兩條邊)。所以為了構造出長度為 \(1\) 的路徑,我們必須從點 \(1\) 向點 \(k + 2\) 連一條長度為 \(1\) 的邊,即 \((1, k + 2, 1)\)。
- 然后依次考慮 \(i = 0, 1, \dots ,k - 1\),連邊 \((i + 2, k + 2, 2^{i})\)。可以發現,在點 \(i + 2\) 之前,所有連向點 \(k + 2\) 的路徑,恰好覆蓋了 \([1, 2^{i}]\) 這些長度。而以點 \(i + 2\) 為終點的路徑,它們覆蓋的長度也是 \([1, 2^{i}]\)。從 \(i + 2\) 向 \(k + 2\) 連一條邊權為 \(w = 2^{i}\) 的邊,相當於把以點 \(i + 2\) 為終點的路徑覆蓋到的長度,平移了 \(w\) 位(\([1, 2^i] \to [2^{i} + 1, 2^{i + 1}]\)),於是現在所有連向 \(k + 2\) 的路徑,就恰好覆蓋了 \([1, 2^{i}]\cup[2^{i} + 1, 2^{i + 1}] = [1, 2^{i + 1}]\)。最終,掃描完 \(i = k - 1\) 后,覆蓋到的區間就恰好是我們需要的 \([1, 2^{k}]\) 了。
於是我們對 \(L = 1, R = 2^k\) 的情況完成了構造。
子問題二:\(L = 1\),\(R\) 任意。
設 \(k = \lfloor\log_2(R - 1)\rfloor\)。
用“子問題一”部分的方法,我們可以先構造出一張 \(k + 2\) 個節點的圖,滿足對任意 \(0\leq i\leq k\),點 \(1\dots i+ 2\) 構成的子圖是 \((1, 2^{i})\)-continuous 的。
新添加一個節點 \(n = k + 3\)。根據前面的分析,我們需要先連一條邊 \((1, n, 1)\)。
設 \(01\) 序列 \(r_{0\dots k}\) 為 \(R - 1\) 的二進制分解。即:\(R - 1 = \sum_{i = 0}^{k} r_i\cdot 2^i\)(\(0\leq r_i\leq 1\))。
對每個 \(i\)(\(0\leq i\leq k\)),如果 \(r_i = 1\),那么我們連一條邊:\((i + 2, n, 1 + \sum_{j = 0}^{i - 1} r_j \cdot 2^{j})\)。其中邊權 \(w = 1 + \sum_{j = 0}^{i - 1} r_j \cdot 2^{j}\) 表示前面已經連出的路徑,覆蓋到的長度恰好是 \([1, w]\)。新的路徑不能和它們重疊,所以要把自己的長度平移 \(w\),即 \([1, 2^{i}] \to [w + 1, w + 2^{i}]\)。
那么最終,我們就能恰好覆蓋長度 \([1, R]\)。
子問題三:\(L > 1\),\(R\) 任意。
先用“子問題二”部分的方法,構造出一張 \((1, R - L + 1)\)-continuous 的圖。設用了 \(n'\) 個節點(\(n' = \lfloor\log_2(R - L)\rfloor + 3\))。
在圖里新建一個節點 \(n = n' + 1\)。
連邊:\((n', n, L - 1)\)。相當於把前 \(n'\) 個點構成的子圖里,所有路徑長度平移了 \(L - 1\)。原來覆蓋到的區間是 \([1, R - L + 1]\),現在覆蓋到的區間就恰好是 \([L, R]\) 了。
最多使用了 \(\lfloor\log_2(R - L)\rfloor + 4 = 23\) 個節點,可以通過本題。
總結:
看到點數 \(n\leq 32\),要聯想到二進制分解。直接思考有些困難,所以按照 \([1, 2^k]\to [1, R]\to [L, R]\) 這樣的思路,有條有理地分析。此外,在 \([1, 2^k]\) 的部分,我們用了“歸納地構造”,這是需要掌握的一個重要技巧。
歸類:二進制分解,歸納。
難度:中。
CF1485D Multiples and Power Differences
觀察到 \(a(i,j)\) 不超過 \(16\),是個很小的數字,並且有 \(\operatorname{lcm}(1,2,\dots,16) = 720720\leq 10^6\)。
記第 \(i\) 行第 \(j\) 列的格子為 \((i, j)\),它的答案為 \(\mathrm{ans}(i, j)\)。則答案可以按如下方法構造:
容易發現,滿足題目的所有要求。
總結:
在網格上,限制條件和“相鄰位置”有關,一般可以考慮把格子按 \(i + j\) 的奇偶性分為兩類。這樣,任意相鄰的兩個格子一定來自不同的類。
歸類:網格,奇偶性。
難度:易。
CF1470C Strange Shuffle
觀察發現,內鬼的影響會逐輪向兩側擴散。
具體來說(以下說的“遞增”、“遞減”都是指非嚴格的,即可能存在等於):
-
對於 \(1\leq i\leq\lfloor\frac{n - 1}{2}\rfloor\),在第 \(i\) 輪后,內鬼左邊的 \(i\) 個數都小於 \(k\),且從內鬼開始向左遞增;內鬼右邊的 \(i\) 個數都大於 \(k\),且從內鬼開始向右遞減。內鬼始終等於 \(k\),距離內鬼超過 \(i\) 的數還未被影響到,所以也等於 \(k\)。
-
對於 \(i > \lfloor\frac{n - 1}{2}\rfloor\),在第 \(i\) 輪后序列里所有數都被影響過了。此時序列里最大的數就是內鬼右邊的數,從它開始向右依次遞減,在內鬼的對面減到 \(k\),然后繼續遞減,直到到達內鬼的左邊,內鬼的左邊就是序列里最小的數。內鬼始終保持為 \(k\)。特別地,\(n\) 為奇數時,“對面”其實是兩個數中間的位置,故可以理解為兩個數一個大於等於 \(k\),一個小於等於 \(k\)。
暴力做法:先在任意位置詢問 \(1\) 次。在接下來的任意時刻,序列里至少有 \(1\) 個大於 \(k\) 的數,它在內鬼的右側;也至少有一個小於 \(k\) 的數,它在內鬼的左側。用 \(n\) 次詢問,暴力找到這樣兩個數。然后在它們之間二分出內鬼的位置。操作次數:\(1 + n + \lfloor\log_2(n)\rfloor\)。
當 \(n\leq 900\) 時,我們就使用上述做法,這可以避免討論很多特殊情況。下面考慮 \(n\) 較大時:
觀察操作次數的限制:\(1000\)。容易聯想到根號做法。設 \(B = \lfloor\sqrt{n}\rfloor\)。先在任意位置詢問 \(B\) 次。在接下來的任意時刻,序列里總存在連續的 \(B\) 個大於 \(k\) 的數,和連續的 \(B\) 個小於 \(k\) 的數。我們每隔 \(B\) 個位置詢問一次,就一定能找出這樣的兩個數。然后再和上面一樣二分答案。操作次數:\(B + \lceil\frac{n}{B}\rceil + \lfloor\log_2(n)\rfloor \approx 2\sqrt{n} + \log_2(n)\),可以通過。
總結:
一開始要多觀察和分析,不要被題面里的上取整、下取整搞暈,可以借助程序,就能發現整個過程的規律其實是簡潔優美的,細節並不繁瑣。
然后分塊和二分,都是交互題里比較經典的做法。
歸類:分塊,二分。
難度:中。
CF1100D Dasha and Chess
先將白棋移到棋盤正中央,也就是 \((500, 500)\) 的位置,這需要花費不超過 \(499\) 步。
如果此時有黑棋也在第 \(500\) 行或第 \(500\) 列,那么直接獲勝。排除這種情況后,現在整個棋盤被我們划分為左上方、右上方、左下方、右下方四個部分,每個部分大小均為 \(499\times 499\)。設四個部分內各有 \(a, b, c, d\) 枚黑棋,則 \(a + b + c + d = 666\)。
我們考慮挑選一個方向,沿着對角線走過去。例如,如果選擇向左上方走,我們就會從 \((500, 500)\) 走到 \((1, 1)\)。我們一共會走 \(499\) 步。稱所選方向以及與它相鄰的兩個方向所對應的區域,為被我們“覆蓋到”的區域(例如,與左上方相鄰的是右上方和左下方。如果向左上方走,那么這三個區域都是被我們覆蓋到的)。
被覆蓋到的區域里的黑棋,如果全程沒有被移動(全程:指從白棋位於 \((500, 500)\) 開始算起,直到白棋沿着對角線走到某個角落為止),那么必定會在某個時刻和白棋共行或共列,這樣我們就贏了。
現在我們要證明,通過合理地挑選方向,一定能使我們覆蓋到的區域里,黑棋數量之和 \(> 499\),這樣在我們掃描完之前,對手來不及把所有黑棋都移出去。
因為 \(a + b + c + d = 666\),根據鴿巢原理可知,\(\min\{a, b, c, d\}\leq \lfloor\frac{666}{4}\rfloor = 166\)。因此如果我們選擇一個方向,使得所覆蓋的三個區域內的黑棋數量之和最大,這個最大值一定 \(\geq 666 - 166 = 500 > 499\)。
於是我們找到了必勝策略,且可以在 \(499 + 499 \leq 2000\) 步以內完成。
值得注意的是,如果在移動過程中,下一步要去的位置上已有一枚黑棋,那不能直接走上去。但此時我們一定可以在一步以內直接取勝(走到一個和它共行或共列的點)。
總結:
通過直覺或靈感,首先想到走到中間。然后使用鴿巢原理。
歸類:網格,鴿巢原理。
難度:中。
CF1081F Tricky Interactor
本題最困難的地方是,操作是不會還原的,而我們並不知道它反轉了哪一邊。於是我們試圖發現一些性質,來判斷它反轉的是哪一邊。
考慮一次反轉產生的影響。設反轉的區間長度為 \(L\),這個區間里原有 \(k\) 個 \(1\)。那么,反轉后區間里會有 \(L - k\) 個 \(1\)。考慮 \(1\) 的變化量,是 \(|k - (L - k)| = |L - 2k|\)。於是可以得到一個重要結論:反轉后【區間里 \(1\) 的變化量】與【區間長度】奇偶性相同。
那么,如果操作 \([l, r]\) 滿足:\([1, r]\) 和 \([l, n]\) 長度的奇偶性不同,我們不就能判斷反轉的是哪一邊了嗎?!
如果每次都能知道反轉的是哪一邊,我們只要寫一個 \(\texttt{while}\) 循環,就可以實現各種想要的反轉效果。舉個例子,我們想要把 \([1, r]\) 反轉。用三個變量 \(x, y, z\) 分別表示 \([1, l), [l, r], (r, n]\) 這三個區間有沒有被反轉過。初始時 \(x = y = z = 0\)。我們想要的最終效果是 \(x = 1, y = 1, z = 0\)(也就是反轉 \([1, r]\))。每次進行一個操作(隨機反轉 \([1, r]\) 或 \([l, n]\)),操作后通過 \(1\) 的變化量的奇偶性,判斷反轉的是哪一邊:如果是 \([1, r]\),則改變 \(x, y\) 的值,否則改變 \(y, z\) 的值。如果達到最終效果就 \(\texttt{break}\),否則繼續操作。直覺上,所需的操作次數應該是很小的常數。可以證明,期望操作次數為 \(3\)。
證明:期望操作次數為 $3$
設 \(E(x, y, z)\) 表示從狀態 \((x, y, z)\) 達到最終狀態 \((1, 1, 0)\) 的期望操作次數。則有轉移式:
- \(E(1, 1, 0) = 0\)。
- \(E(x, y, z) = \frac{1}{2}(E(x\operatorname{xor}1, y\operatorname{xor}1, z) + E(x, y\operatorname{xor}1, z\operatorname{xor}1)) + 1\)。
自己寫一個高斯消元,不難解得:\(E(0, 0, 0) = 3\)。
利用上述的分析來構造方案。考慮三種情況:
- 如果 \(n = 1\),輸入的數就是答案,直接輸出即可。
- 如果 \(n\) 是偶數,那么對於所有 \(i\),\([1, i]\) 與 \([i, n]\) 長度的奇偶性不同,於是可以用上述的方法實現【把 \([1, i]\) 反轉】。具體來說,我們從小到大遍歷所有位置 \(i\)。先將 \([1, i]\) 反轉。通過【原序列里 \(1\) 的數量】和【反轉后整個序列里 \(1\) 的數量】,可以計算出原序列 \([1, i]\) 里 \(1\) 的數量。減去上一步算出的 \([1, i - 1]\) 里 \(1\) 的數量,就知道第 \(i\) 位的答案了。最后把 \([1, i]\) 再反轉一次,將序列還原回去。再繼續考慮下一個位置。如此可以求出所有位置的答案。
- 如果 \(n\) 是大於 \(1\) 的奇數,那么對於所有 \(i\geq 2\),\([1, i]\) 與 \([i - 1, n]\) 長度的奇偶性不同。如果知道了位置 \(1\) 的答案,那么可以用類似的方法可以依次推出 \(2\dots n\) 的答案(也就是每次對區間 \([i - 1, i]\) 操作,實現反轉 \([1, i]\))。考慮求位置 \(1\) 的答案,可以對區間 \([2, n]\) 操作,實現反轉 \([2, n]\),從而可以算出位置 \(1\) 的答案。
因為每次“反轉 \([1, i]\)”期望需要 \(3\) 次操作,反轉后我們還要還原,所以總共期望需要 \(6n\) 次操作,可以通過本題。
總結:
首先分析:因為操作不還原,如果無法確定反轉的是哪一邊,那么是很難做的。於是我們試圖發現一些性質,來判斷它反轉的是哪一邊。進而我們分析出了奇偶性的這個性質。利用這個性質,不難構造出操作的方法。
歸類:奇偶性。
難度:中。
CF1188A2 Add on a Tree: Revolution
請注意重要條件:所給局面中,邊權都是偶數,且互不相同。
引理:有解當且僅當不存在度數為 \(2\) 的點。
必要性是很顯然的。因為如果存在度數為 \(2\) 的點,考慮它連接的兩條邊,每次操作,這兩條邊要么都不被經過,要么一起被經過。所以最終局面下,這兩條邊的邊權一定相同。又因為題目所給局面里邊權互不相同,矛盾了。所以:若有解,一定不存在度數為 \(2\) 的點。
充分性,我們通過構造來證明。
首先,對於任意葉子節點 \(u\)、任意其他節點 \(v\)、以及任意偶數 \(x\),可以實現一種基本操作是:給 \((u, v)\) 路徑上所有邊邊權加上 \(x\),且不改變其他邊的邊權。具體做法是:
- 如果 \(v\) 也是葉子節點,一步操作即可實現。
- 如果 \(v\) 不是葉子節點,那么 \(v\) 度數至少為 \(3\)。不妨以 \(v\) 為根。考慮 \(v\) 除了包含 \(u\) 的兒子外的另外兩個兒子,記為 \(s_1, s_2\)。從 \(s_1, s_2\) 的子樹里各取一個葉子,記為 \(l_1, l_2\)。那么可以通過如下三次操作,實現我們想要的效果:
- 對 \((l_1, u)\) 的路徑加上 \(\frac{x}{2}\)。
- 對 \((l_2, u)\) 的路徑加上 \(\frac{x}{2}\)。
- 對 \((l_1, l_2)\) 的路徑加上 \(-\frac{x}{2}\)。
有了上述基本操作后,可以這樣構造出本題的解法:取一個度數為 \(1\) 的節點為根,記為 \(r\)。從 \(r\) 出發 dfs。具體來說,我們實現一個函數:\(\text{solve}(u)\),它的任務是,通過操作使得 \(u\) 子樹內所有邊邊權都變為 \(0\),且可以任意改變 \(u\) 到根路徑上的邊權,但不允許改變 \(u\) 子樹外其他邊的邊權。同時,要保證任意時刻所有邊邊權均為偶數。
考慮 \(u\) 的每個兒子 \(v\)。先調用 \(\text{solve}(v)\),那么此時 \(v\) 的子樹已經被解決了,之后我們不會再去碰它。考慮 \((u, v)\) 這條邊此時的邊權,記為 \(x\)(\(x\) 一定是偶數)。我們通過上述基本操作,令 \(r\) 到 \(v\) 路徑上所有邊邊權加上 \(-x\)。這樣使得 \((u, v)\) 的邊權變為 \(0\),且 \(u\) 到根路徑上的邊權仍然都是偶數。
把上述的【將所有邊邊權變為 \(0\) 的】的過程反過來(加變成減,減變成加),就是答案了。
朴素的實現方法,就是在操作時暴力更改所有祖先的邊權,時間復雜度 \(\mathcal{O}(n^2)\)。也可以用樹上差分來優化,時間復雜度 \(\mathcal{O}(n)\)。
總結:
一開始不知道有解的條件,我們就先蒙一個顯然的必要條件。然后在滿足這個條件的前提下,嘗試去構造解法。如果構造出來了,說明該條件也是充分的。
首先構造出一個基本操作,它像是我們的磚頭,我們用它來蓋大房子。
樹上的問題,要注意到樹本身特有的、“遞歸式”的結構,以子樹作為天然的子問題,遞歸地解決。
歸類:樹,遞歸。
難度:中。
GYM102392C Find the Array
第一步:找出最大或最小值的位置。
先詢問下標集合 \([1, n]\)。得到的答案集合里的最大值,顯然是序列中的 \(\text{最大值} - \text{最小值}\),即 \(\max\{a_i\} - \min\{a_i\}\),記為 \(D\)。
接下來考慮對某個 \(i\),詢問下標集合 \([1, i]\)。記得到的答案集合里的最大值為 \(d\)。顯然 \(d\leq D\)。並且如果 \(d < D\),說明原序列的最大、最小值至少有一個下標大於 \(i\)。
根據這個觀察,可以二分求出最大或最小值的位置,記為 \(p\)(此時只知道 \(p\) 上是最大或最小值,但具體是最大還是最小我們並不清楚)。
然后我們試圖確定,位置 \(p\) 上究竟是最大值還是最小值。任取一個其他位置 \(q\) (\(q\neq p\)),詢問 \(a_p\), \(a_q\) 后,比較它們的大小關系即可。
至此一共使用了 \(1 + \lfloor\log_2(n)\rfloor + 2\leq 10\) 次詢問,找出了最大或最小值的位置,並且知道了它是最大值還是最小值。
第二步:二進制分組。
因為 \(a_p\) 已知,所以我們只要求出每個數和 \(a_p\) 的差,就相當於求出了這個數的值(這里 \(a_p\) 是最大或最小值的好處就體現在,絕對值符號不會困擾我們了)。
對一個下標的集合 \(S\) (\(p\notin S\)),可以用 \(2\) 次詢問求出里面的數的集合 \(A(S) = \{a_i | i \in S\}\):
- 先詢問 \(S\),記答案集合為 \(a\)。
- 再詢問 \(S \cup\{p\}\),記答案集合為 \(b\)。則 \(A(S) = b\setminus a\),也就是從 \(b\) 里把所有 \(a\) 中的數扣除一次后,得到的集合。
特別地,如果 \(p\in S\),我們先用上述方法對 \(S\setminus \{p\}\) 詢問,然后在結果里加入 \(a_p\) 即可。
接下來我們把下標按二進制分組,然后對每組用上述方法詢問。具體來說,對所有二進制位 \(j\) (\(0\leq j\leq \lfloor\log_2(n)\rfloor\)),用兩次詢問,求出所有【下標第 \(j\) 位上為 \(1\)】的數的集合,記為 \(F(j) = \{a_i | i \operatorname{and} 2^j \neq 0\}\)。
因為每個下標 \(i\) 的二進制表示是唯一的,這意味着,對所有 \(i\),\(a_i\) 被分配到的集合互不相同。具體來說,假設 \(i\) 在 \(u_0, u_1, \dots, u_x\) 這些二進制位上為 \(1\),\(v_0, v_1, \dots, v_{y}\) 這些二進制位上為 \(0\),那么 \(a_i\) 一定在所有 \(F(u_0), F(u_1), \dots, F(u_x)\) 里出現過,並且在 \(F(v_0), F(v_1), \dots, F(v_y)\) 里都沒有出現過,並且這樣的數有且僅有一個(如果存在第二個,說明它們下標的二進制表示完全相同),找出這個數,它就是 \(a_i\)。
對每個二進制位都要問兩次。所以所需的詢問次數是:\(\lfloor\log_2(n)\rfloor\cdot 2 \leq 16\) 次。
總詢問次數不超過 \(10 + 16 = 26\) 次。
時間復雜度很松,朴素的 \(\mathcal{O}(n^3\log n)\) 實現即可通過。
總結:
因為絕對值不好處理,所以想到先求出最大或最小值。然后就可以實現查詢一個集合。於是常用的技巧是把數按二進制編碼,分成若干個集合,分別查詢即可。
歸類:二分,二進制編碼,猜數列。
難度:中。
Part2 wzy 課件
CF1375H Set Merging
朴素的做法是,把區間 \([l_i, r_i]\) 里的數值從小到大排序,依次合並。共需要 \(\mathcal{O}(qn)\) 次合並,太多了。
優化上述做法,考慮對值域分塊。設塊的大小為 \(B\),分出了 \(\lceil\frac{n}{B}\rceil\) 個塊。我們在每一塊內,把元素按照原序列里的出現位置排序。也就是說,每個塊都是原序列的一個子序列。
考慮查詢 \([l_i, r_i]\) 時,我們從小到大遍歷所有塊,把每一塊里位置在 \([l_i, r_i]\) 內的元素提取出來,顯然每一塊里提取出的一定是一段連續的元素。假設它們的集合是已知的,那么只需要把所有塊對應的集合依次合並即可。單次詢問需要 \(\mathcal{O}(\frac{n}{B})\) 次合並操作。在每個塊里提取區間時,可能需要二分,所以總時間復雜度是 \(\mathcal{O}(q\frac{n}{B}\log B)\)。
那么問題轉化為,預處理出每個塊內所有區間對應的集合。這樣的區間共有 \(\mathcal{O}(\frac{n}{B}\cdot B^2) = \mathcal{O}(nB)\) 個。
每個塊單獨預處理。在值域上分治。具體來說,我們會發現,原序列的一段區間,在某一段值域里的數,可以表示為該區間的一個子序列。在分治的第一層,這個子序列就是我們的當前塊,設它為 \(p_1, p_2, \dots ,p_{|p|}\)。下面把值域一分為二:\([l, m], [m + 1, r]\)。那么子序列 \(p\),又會對應地划分為兩個子序列:\(u_1, u_2, \dots ,u_{|u|}\) 和 \(v_1, v_2, \dots, v_{|v|}\)。其中 \(l\leq a_{u_i}\leq m\),\(m < a_{v_i}\leq r\),並且 \(|u| = m - l + 1\),\(|v| = r - m\),\(u \cup v = p\)。
我們有 \(\mathcal{O}(B^2)\) 個區間需要求答案(這個“答案”就是指構造出的一個集合)。遞歸下去。對每個區間,求出它在 \(u\) 序列上的答案,和在 \(v\) 序列上的答案,然后用一次操作合並起來,就能得到它在 \(p\) 序列上的答案。
朴素實現所需的操作次數是 \(\mathcal{O}(B\log B\cdot B^2)\),因為總共遞歸地調用了 \(\mathcal{O}(B\log B)\) 次函數,每次對 \(\mathcal{O}(B^2)\) 個集合更新答案。這樣太多了。考慮某個需要求答案的區間 \([i, j]\)(注意這里 \(i, j\) 指的是位置,即原序列里的下標,而不是數值。\(l, r, m\) 這些都是數值,我們在值域上做的分治),它在 \(p\) 序列上的答案,等同於區間 \([p_{i'}, p_{j'}]\) 的答案。其中 \(p_{i'}\) 是 \(p\) 序列里第一個 \(\geq i\) 的元素,\(p_{j'}\) 是 \(p\) 序列里最后一個 \(\leq j\) 的元素。因此我們只需要對 \(\mathcal{O}(|p|^2)\) 個區間求答案(而不是原來的 \(\mathcal{O}(B^2)\) 個)。分析現在的操作次數,設某次調用分治函數時,\(r - l + 1 = L\),則操作次數為 \(T(L) = 2T(\frac{L}{2}) + \mathcal{O}(L^2)\),解得 \(T(L) = \mathcal{O}(L^2)\)。所以現在操作次數被優化到 \(\mathcal{O}(B^2)\),可以接受。
還有一個小問題就是找 \(i', j'\)。可以用主席樹查詢,但沒必要。我們從前往后、從后往前掃描兩遍 \(p\) 序列,就能對所有 \(i,j \in p\),推出它們對應的 \(i', j'\)。所以整個分治的時間復雜度也是 \(\mathcal{O}(B^2)\)。
對每個塊都預處理一次,總共所需操作次數和總時間復雜度都是:\(\mathcal{O}(\frac{n}{B}\cdot B^2) = \mathcal{O}(nB)\)。
綜上,分析一下所需的操作次數,是 \(\mathcal{O}(nB + q\frac{n}{B})\)。顯然取 \(B = \sqrt{q} = 2^{8}\) 時是最優的。操作次數是 \(\mathcal{O}(n\sqrt{q})\)。
時間復雜度 \(\mathcal{O}(nB + q\frac{n}{B}\log B) = \mathcal{O}(n\sqrt{q}\log(\sqrt{q}))\)。
總結:
上述做法的本質是:將一個序列按值域范圍划分成若干子序列之后,原序列的每個連續區間都可以表示成划分成的子序列中的連續區間的並。
分塊和分治都是對這樣本質的具體實現(分出的塊是這樣的子序列,分治時處理的 \(p\) 數組也是這樣的子序列)。將它們結合起來,就能得到最優的做法。
歸類:分塊,值域分塊。
難度:難。
CF1364E X-OR
設 \(s\) 為可能涉及到的最大二進制位,即:\(s = \lfloor\log_2(n - 1)\rfloor\leq 10\)。
解題的核心是:找出 \(p_i = 0\) 的位置 \(i\),然后把每個位置都和 \(i\) 問一遍,即可得到答案。
如何找到 \(0\) 所在的位置?有如下的一些方法:
法一:
考慮實現這樣一個函數 \(f\),傳入任意一個位置 \(i\),它能問出 \(p_i\) 的值。
為了實現這個函數 \(f\),我們構造一個序列 \(z_0, z_1, \dots, z_{s}\),滿足 \(p_{z_j}\) 的二進制第 \(j\) 位為 \(0\)。有了 \(z\) 序列后,函數 \(f\) 就很好實現了,它返回的結果就是 \(q(i, z_0)\operatorname{and}q(i, z_1)\operatorname{and}\dots \operatorname{and}q(i, z_s)\),其中 \(q(x, y)\) 表示用一次詢問,問出的 \(p_x\operatorname{or} p_y\) 的值。這是因為,如果 \(p_i\) 的第 \(j\) 位為 \(1\),那么返回的結果第 \(j\) 位一定是 \(1\);如果 \(p_i\) 的第 \(j\) 位為 \(0\),那么 \(q(i, z_j)\) 的第 \(j\) 位是 \(0\),所以返回的結果第 \(j\) 位也是 \(0\)。調用一次函數 \(f\),需要 \(s + 1\) 次詢問。
下面問題轉化為如何構造 \(z\) 序列。可以每次隨機兩個位置 \(x, y\)(\(x,y\in[0, n - 1], x\neq y\)),詢問 \(p_x\operatorname{or} p_y\) 的值。對結果里所有為 \(0\) 的二進制位 \(j\),令 \(z_j\gets x\)(或 \(y\))即可。因為任意二進制位 \(j\) 都有至少 \(\frac{n}{2}\) 個數這位為 \(0\),所以期望 \(4\) 次就能隨出 \(z_j\),總共 \(40\) 次詢問就能得到 \(z\) 序列(實際是小於這個數的,因為每次詢問可以更新多個二進制位)。
有了 \(z\) 序列后,如果暴力問出每個 \(p_i\),總詢問次數是:\(40 + n\cdot(s + 1)\),無法通過。
考慮找出 \(p_i = 0\) 的位置 \(i\)。初始時,先令 \(i = 0\),並且通過函數 \(f\) 問出 \(p_0\) 的值。然后依次考慮每個位置,設當前考慮到的位置為 \(i'\)。用一次詢問,查詢 \(p_{i}\operatorname{or}p_{i'}\),如果結果等於 \(p_i\),說明 \(p_{i'}\) 是 \(p_i\) 的子集。此時令 \(i\gets i'\),並通過函數 \(f\) 暴力問出新的 \(p_i\) 的值。掃描完所有位置后,最終的 \(i\) 就是我們要找的 \(p_i = 0\) 了。
因為每次 \(i\) 變化時,\(p_{i'}\) 都是 \(p_i\) 的子集且 \(p_{i'}\neq p_i\),所以至少減少一個二進制位,也就是 \(i\) 最多變化 \(s\) 次。因此總共調用不超過 \((s + 1)\) 次函數 \(f\)。另外,每次查詢 \(p_{i}\operatorname{or}p_{i'}\) 還需要 \(n - 1\) 次詢問。
最后,讓 \(i\) 和其他所有位置都問一遍(\(n - 1\) 次詢問),求出答案。
總共需要的詢問次數是 \(40 + (s + 1)^2 + (n - 1) + (n - 1) = 4257\) 次。可以通過本題。
法二:
依次考慮所有位置。維護兩個位置 \(a, b\),表示在當前掃描到的前綴里,\(0\) 只可能在位置 \(a\) 或位置 \(b\) 上。並且記錄下 \(p_a \operatorname{or} p_b\) 的值。設當前位置為 \(c\),則有如下情況:
- 若 \(p_a \operatorname{or} p_c > p_a \operatorname{or} p_b\),則 \(p_c\) 不可能是 \(0\)。
- 若 \(p_a \operatorname{or} p_c < p_a \operatorname{or} p_b\),則 \(p_b\) 不可能是 \(0\)。我們把 \(b\) 踢掉,把 \(c\) 加入。
- 若 \(p_a \operatorname{or} p_c = p_a \operatorname{or} p_b\),則 \(p_a\) 不可能是 \(0\)(否則 \(p_b = p_c\),不合題意)。我們把 \(a\) 踢掉,把 \(c\) 加入。
每次需要詢問 \(p_a \operatorname{or} p_c\)。另外,如果是情況 3(把 \(a\) 踢掉了),相當於把 \(c\) 作為新的 \(a\),所以還需要詢問出新的 \(p_a \operatorname{or} p_b\) 的值。所以最多可能要 \(2n\) 次詢問。
求出最終的 \(a, b\) 后,隨機一些位置 \(t\)。若 \(p_a \operatorname{or} p_t \neq p_b \operatorname{or} p_t\),就能知道哪個位置是 \(0\) 了。也就是說,我們要隨出一個位置 \(t\),使得 \(p_t\) 不是 \(p_b\) 的超集。那么考慮 \(p_b\) 里任意一個為 \(1\) 的二進制位,我們只需要隨出一個 \(p_t\) 這一位上是 \(0\) 就可以了。因為每一位為 \(0\) 的數至少有 \(\frac{n}{2}\) 個,所以期望只需要隨機 \(2\) 次。
最后,知道了 \(0\) 的位置后,還要和每個數問一遍,求出答案。所以上述做法需要 \(3n + 2\) 次詢問,無法通過。
不過,我們可以以隨機順序訪問所有位置。這樣每次加入 \(c\) 時,情況 3 發生的概率是很小的。設情況 3 發生的次數為 \(k\),則所需詢問次數是 \(2n + 2 + k\)。可以通過本題(因為 \(k\) 是按我們隨機的順序來的,所以和題目的輸入無關。因此你只需要自己隨機幾次,就可以驗證了)。
暫無參考代碼。
總結:
核心是要找出 \(p_i = 0\) 的位置 \(i\)。兩種方法,分別是利用了 \(\operatorname{and}\) 和 \(\operatorname{or}\) 運算的特性。還是挺巧妙的。
歸類:位運算,猜數列。
難度:中。
CF1365G Secure Password
每次詢問,可以查詢出一個集合里所有數的按位或。考慮通過某種構造方法,使得對每個位置 \(i\)(\(1\leq i\leq n\)),都能選出若干(被詢問過的)集合,滿足:\(i\) 不在這些集合里,除 \(i\) 外的所有位置都被包含在至少一個集合里。如果能構造出一組滿足上述條件的集合(不超過 \(13\) 個),那么本題就做完了。
考慮使用二進制編碼。第一反應有一個較為簡單的做法:
-
因為 \(n\leq 1000\),那么每個位置 \(i\) 都可以對應一個 \(10\) 位二進制數(可以有前導零)。
-
對每個二進制位,我們把所有這位是 \(0\) 的位置的按位或值問出來;所有這位是 \(1\) 的位置的按位或值問出來。
-
查詢位置 \(i\) 的答案,對每個二進制位 \(j\)(\(0\leq j < 10\)),設 \(i\) 的第 \(j\) 位為 \(t\),那么就把答案或上第 \(j\) 位與 \(t\) 相反的那個集合。
-
正確性:
- 顯然位置 \(i\) 不會被選中的集合包含。因為是按每一位取反選的,所以所選集合里的數,至少有一位與 \(i\) 不同。
- 所有 \(\neq i\) 的位置都會被包含在至少一個集合里。因為其他位置至少會有一位與 \(i\) 不同。
-
上述做法所需的詢問次數是 \(20\) 次。無法通過本題,需要進一步優化。
上述做法所需的詢問次數太多了。下面有一個更妙的做法:
- 把所有 \(13\) 位的、恰好有 \(6\) 個 \(1\)的二進制數拿過來,作為編碼。因為 \({13\choose 6} > 1000\geq n\),因此一定可以使得:每個位置 \(i\) 唯一對應一個編碼。
- 對每個二進制位 \(j\)(\(0\leq j < 13\)),把編碼的第 \(j\) 位上是 \(1\) 的位置拿出來,詢問他們的按位或,記為 \(w_j\)。
- 查詢位置 \(i\) 的答案。對每個二進制位 \(j\)(\(0\leq j < 13\)),如果 \(i\) 的編碼的第 \(j\) 位為 \(0\),那么就令答案或上 \(w_j\)。
- 正確性:
- 顯然位置 \(i\) 不會被包含。
- 所有 \(\neq i\) 的位置,至少存在某一位,使得它的編碼這一位上是 \(1\) 而 \(i\) 的編碼這一位上是 \(0\)。這是因為我們保證了所有編碼里 \(1\) 的個數相同。
- 需要 \(13\) 次詢問,可以通過本題。
歸類:二進制編碼。
難度:中。
CF1290D Coffee Varieties (hard version)
以下僅討論較為一般的情況(\(1 < k < n\))。其他情況留給讀者自行特判。
朴素暴力:
要對元素去重,可以考慮判斷每一對元素是否相同。具體來說,對於所有二元組 \((i, j)\)(\(1\leq i < j\leq n\)),先清空隊列,然后把 \(i\) 加入隊列,再把 \(j\) 加入隊列。如果 \(j\) 和 \(i\) 兩元素相同,就把 \(j\) 打上“死亡標記”。最后答案就是沒有被打上死亡標記的元素數量。
上述做法太暴力了。考慮不清空隊列。對所有二元組 \((i_1, j_1), (i_2, j_2), \dots, (i_{\frac{n(n - 1)}{2}}, j_{\frac{n(n - 1)}{2}})\),依次將元素:\(i_1, j_1, i_2, j_2, \dots, i_{\frac{n(n - 1)}{2}}, j_{\frac{n(n - 1)}{2}}\) 加入隊列。加入一個數時,若隊列里存在和它相同的數,就把該數打上死亡標記。但這樣(不清空隊列)會帶來一個問題:可能隊列里和它相同的那個數就是它自己!那么我們可能就把某個數值的唯一一次出現給刪了。
為了避免自己把自己刪掉的情況,我們不得不使用一些清空操作。考慮這樣一個問題:一張 \(n\) 個點的有向圖,對所有 \(i\),從點 \(i\) 向點 \(i + 1, i + 2, \dots, n\) 連邊。求將圖划分為盡量少的路徑,使得每條邊恰好出現在一條路徑中。我的構造方法是:考慮枚舉間隔 \(d = 1, 2, \dots, n - 1\)。
- 所有【端點間隔為 \(1\) 的邊】只需要 \(1\) 條路徑就能串起來。
- 所有【端點間隔為 \(2\) 的邊】需要划分為 \(2\) 條路徑:起點為 \(1\) 的路徑和起點為 \(2\) 的路徑。
- ......
- 所有【端點間隔為 \(\frac{n}{2}\) 的邊】需要划分為 \(\frac{n}{2}\) 條路徑。
- 所有【端點間隔為 \(\frac{n}{2} + 1\) 的邊】需要划分為 \(\frac{n}{2} - 1\) 條路徑,因為起點為 \(\frac{n}{2}\) 時就沒有【端點間隔為 \(\frac{n}{2} + 1\) 的邊】了。
- ......
- 所有【端點間隔為 \(n - 1\) 的邊】需要划分為 \(1\) 條路徑。
總路徑數是:\(\frac{(\frac{n}{2} + 1)\frac{n}{2}}{2} + \frac{\frac{n}{2}(\frac{n}{2} - 1)}{2} = \frac{n^2}{4}\)。
那么需要的清空次數也是 \(\frac{n^2}{4}\)。詢問次數 = 總邊數 + 划分出的路徑數 = \(\frac{n(n - 1)}{2} + \frac{n^2}{4}\)。無法通過本題。
分塊暴力:
上述做法難以通過,是因為沒有充分利用隊列長度為 \(k\) 的特點。
我們考慮分塊:每 \(\frac{k}{2}\) 個數分為一塊,分出 \(\frac{2n}{k}\) 塊。
把任意一個塊里的所有元素加入隊列(隊列里有相同元素就打死亡標記),相當於實現了塊內去重。下面考慮不同塊之間的去重。暴力枚舉一對塊 \((i, j)\)(\(1\leq i < j\leq \frac{2n}{k}\)),把隊列清空,然后將兩個塊依次加入隊列。
所需的清空次數,即塊的無序對數,為 \(\frac{\frac{2n}{k}(\frac{2n}{k} - 1)}{2} = \frac{2n^2}{k^2} - \frac{n}{k} \leq \frac{2n^2}{k}\leq 20000\)。
所需的詢問次數,是無序對數乘以 \(k\),即 \(\frac{\frac{2n}{k}(\frac{2n}{k} - 1)}{2}\cdot k = \frac{2n^2}{k} - n\)。可以通過本題的 easy version。
結合一下:
我們發現,分塊做法在處理二元組 \((i,i + 1), (i, i + 2), \dots,(i, n)\) 時,都要先清空隊列,再重新加入第 \(i\) 塊。這樣是非常虧的。
考慮把分塊和“朴素暴力”里的做法相結合。即,不用每次都只加入一個二元組,然后清空。我們把二元組看做邊,那么可以每次加入一條路徑,然后再清空。
在朴素暴力部分,我們知道,一張 \(n\) 個節點的圖,有 \(\frac{n(n - 1)}{2}\) 條邊,可以划分成 \(\frac{n^2}{4}\) 條路徑。現在通過分塊,我們把節點數壓縮到了 \(\frac{2n}{k}\)。所以邊數是 \(\frac{\frac{2n}{k}(\frac{2n}{k} - 1)}{2} = \frac{2n^2}{k^2}-\frac{n}{k}\),划分出的路徑數是 \(\frac{n^2}{k^2}\)。
每條邊,以及每條路徑的起點,都需要 \(\frac{k}{2}\) 次詢問操作(即把一個塊加入隊列)。所以需要的詢問操作數是:\(\frac{k}{2}(\frac{2n^2}{k^2}-\frac{n}{k} + \frac{n^2}{k^2}) = \frac{3n^2}{2k} - \frac{n}{2}\)。可以通過本題。
更牛一點:
發現在上述的,從 \(i\) 向 \(i + 1, i + 2, \dots ,n\) 連邊的有向圖中,我們已經難以構造出更優的划分方案。
不妨退一步,把圖擴充一下,變成完全圖,即 \(i\) 向所有 \(j\neq i\) 連邊。另外注意,此時划分出的每條路徑,一定不能多次經過同一個節點,否則可能出現“隊列里和它相同的那個數就是它自己”(造成誤刪除)的情況。在原來的圖上不會有這種情況,因為每個節點都無法走到自己。
完全圖的性質更好,有更漂亮的划分方法:之字形划分(官方題解中稱為 zig-zag pattern)。枚舉所有起點 \(s\)(\(s = 1, 2, \dots n\))。走出一條不經過重復點的路徑:\(s \to (s - 1)\to (s + 1)\to (s - 2)\to (s + 2),\dots\)。可以理解為把點排成一圈。手動模擬一下,發現這樣每條邊恰好被覆蓋一次。
並且由於擴充為了有向圖,我們可以讓塊的大小從 \(\frac{k}{2}\) 變成 \(k\)。因為任意兩個塊正着反着都會被拼一次,所以效果和原來是一樣的。這樣邊數和路徑數,分別被優化為了 \(\frac{n^2}{k^2} - \frac{n}{k}\) 和 \(\frac{n}{k}\)。總詢問次數是:\(k(\frac{n^2}{k^2} - \frac{n}{k} + \frac{n}{k}) = \frac{n^2}{k}\)。非常優秀。
總結:
首先,分塊是一個常見的優化。然后要建立圖論的模型,這需要對問題性質的深入分析。最后還要在圖上構造出划分方案,比較考驗構造能力。
歸類:分塊,圖划分。
難度:難。
CF1292E Rin and The Unknown Flower
暴力做法:花費 \(2\) 問 \(\texttt{C},\texttt{O}\),剩下的位置就是 \(\texttt{H}\)。
這種做法給了我們一些啟發,事實上可以稍微加長一下詢問的串長。
我們詢問 \(\texttt{CC}, \texttt{CH}, \texttt{CO}\)。這樣就能以 \(3\times \frac{1}{2^2} = 0.75\) 的代價,知道(除最后一個位置外)所有 \(\texttt{C}\) 出現的位置。
同理,我們還想知道所有 \(\texttt{O}\) 出現的位置。不過考慮到前面已經問過一次 \(\texttt{CO}\),所以只需要再來兩次詢問 \(\texttt{HO}, \texttt{OO}\),就能知道(除第一個位置外)所有 \(\texttt{O}\) 出現的位置。
至此一共使用了 \(5\) 次詢問(\(\texttt{CC}, \texttt{CH}, \texttt{CO}, \texttt{HO}, \texttt{OO}\)),代價為 \(5\times \frac{1}{2^2} = 1.25\)。串里所有還不確定的位置,除第一個和最后一個位置外,它的字符一定是 \(\texttt{H}\)。於是我們已知了除第一個和最后一個位置外的所有字符。
接下來處理第一個和最后一個位置(如果它們仍然未知的話)。第一個位置只可能是 \(\texttt{O},\texttt{H}\),最后一個位置只可能是 \(\texttt{C}, \texttt{H}\)。共有 \(2\times 2 = 4\) 種可能。我們只需要進行 \(3\) 次長度為 \(n\) 的詢問(如果都不成功,則必然是最后一種,不需要問了)。
至此花費的總代價為 \(1.25 + \frac{3}{n^2}\)。在 \(n > 4\) 時可以成功。
接下來單獨處理 \(n = 4\) 的情況。
仍然先詢問 \(\texttt{CC}, \texttt{CH}, \texttt{CO}\)。如果至少有一次出現,那么至多只剩兩位不確定。且前 \(3\) 位(如果還不確定的話)不可能是 \(\texttt{C}\),所以至多只有 \(2\times 3 = 6\) 種可能,只需要 \(5\) 次詢問。花費的總代價是:\(3\times \frac{1}{2^2} + 5\times \frac{1}{4^2} = 1.0625\)。
如果 \(\texttt{CC}, \texttt{CH}, \texttt{CO}\) 都沒有出現過,再詢問 \(\texttt{HO}\)。如果它出現過,可以用類似的方法,以 \(4\times \frac{1}{2^2} + 5\times \frac{1}{4^2} = 1.3125\) 的代價求出答案。
如果 \(\texttt{CC}, \texttt{CH}, \texttt{CO}, \texttt{HO}\) 都沒有出現過,再問 \(\texttt{OO}\)。如果 \(\texttt{OO}\) 出現過,那么目前已知的串,可能有如下三種情形:
- \(\texttt{OOOO}\)。此時答案已知。
- \(\texttt{OOO*}\)。即只有第 \(4\) 位未知,並且它只可能是 \(\texttt{C}\) 或 \(\texttt{H}\)(是 \(\texttt{O}\) 的話已經被問出來了)。
- \(\texttt{OO**}\)。即第 \(3\) 和第 \(4\) 位未知。此時第 \(3\) 位必是 \(\texttt{H}\)(如果是 \(\texttt{O}\) 或 \(\texttt{C}\) 它一定已經被問出來了)。第 \(4\) 位只可能是 \(\texttt{C}\) 或 \(\texttt{H}\)。
注意,不可能出現 \(\texttt{**OO}\) 或 \(\texttt{*OOO}\) 的情況。因為此時兩個 \(\texttt{O}\) 前面,不可能是 \(\texttt{C}\)(否則在 \(\texttt{CO}\) 就被問出來了),不可能是 \(\texttt{H}\)(否則在 \(\texttt{HO}\) 就被問出來了),也不可能是 \(\texttt{O}\)(否則在 \(\texttt{OO}\) 就被問出來了)。
綜上所述,最多只有 \(1\) 個位還不確定,且它最多只有 \(2\) 種情況。所以只需要再來 \(1\) 次詢問。總代價是:\(5\times\frac{1}{2^2} + 1\times\frac{1}{4^2} = 1.3125\)。
最后,如果 \(\texttt{CC}, \texttt{CH}, \texttt{CO}, \texttt{HO}, \texttt{OO}\) 都沒有出現過,那么中間兩位一定是 \(\texttt{HH}\)。此時詢問 \(\texttt{HHH}\)。第一位如果是 \(\texttt{H}\),那么它會被問出來,否則它一定是 \(\texttt{O}\)。同理,最后一位也可以確定。總代價是:\(5\times \frac{1}{2^2} + 1\times\frac{1}{3^2} = 1.3611\)。
於是就做完了。實現的時候,可以把每個位置可能的選項,用一個 vector 存起來,然后 \(\text{dfs}\) 一遍。這樣可以避免復雜的討論。詳見參考代碼。
總結:
要想到第一步:即用 \(5\) 次詢問確定(除第一位和最后一位以外的)\(\texttt{C}\) 和 \(\texttt{H}\)。剩下的用耐心推一推,分類討論一下即可。
歸類:分類討論。
難度:難。
CF1097E Egor and an RPG game
約定:以下稱一個單調上升或單調下降的子序列,為“合法子序列”。稱最長上升子序列為 \(\text{LIS}\),最長下降子序列為 \(\text{LDS}\)。
考慮 \(f(n)\) 是多少。
我們想辦法構造出一個“划分出的合法子序列數”很大的排列。考慮如下序列:
即,它是分層上升的,每層比上一層多一個數,且同一層內是嚴格遞減的。
當存在某個正整數 \(k\),滿足 \(n = 1 + 2 + \dots + k = \frac{k(k + 1)}{2}\) 時,這個序列無論划分成上升還是下降,都至少要分出 \(k\) 個合法子序列。更進一步,如果 \(\frac{k(k + 1)}{2}\leq n < \frac{(k + 1)(k + 2)}{2}\),則用類似的構造方法,也可以使得一個長度為 \(n\) 的序列,至少分出 \(k\) 個合法子序列。換句話說,現在我們說明了:
記 \(c(n) = \max\left\{k \ \big |\ \frac{k(k + 1)}{2} \leq n\right\}\)。
現在,如果我們能夠想到一種方法,使得對任意長度為 \(n\) 的排列,都能將它划分為不超過 \(c(n)\) 個合法的子序列,我們的任務就完成了。因為:我們划分出的子序列數 \(\leq c(n)\),而 \(c(n)\leq f(n)\)。
用歸納法。假設命題【對任意 \(k \geq c(m)\),總能把一個長度為 \(m\) 的排列划分為不超過 \(k\) 個合法子序列】在 \(1\leq m < n\) 時成立。接下來我們證明它在 \(m = n\) 時也是成立的。
求出當前序列的一個 \(\text{LIS}\),記它的長度為 \(l\)。
情況一:若 \(l > c(n)\),則將它作為新划分出的一個子序列,加入答案。之后問題規模縮減為:
對原來的 \(n, k\),我們有:\(n < \frac{(k + 1)(k + 2)}{2}\)。發現刪除后,\(n - l < \frac{(k + 1)(k + 2)}{2} - (k + 1)= \frac{k(k + 1)}{2}\),即 \(n' < \frac{(k' + 1)(k' + 2)}{2}\)。所以 \(c(n')\leq k'\)。此時根據歸納假設,可知原命題成立。
情況二:若 \(l \leq c(n)\)。發現我們總能將原序列划分為恰好 \(l\) 個下降子序列。其實就是在用二分求 \(\text{LIS}\) 的算法中,每次二分出第一個大於當前數的位置后,不直接覆蓋,而是把當前數加入到它代表的下降子序列中。具體可以見代碼。
綜上所述,原命題成立。因此用上述方法構造出的划分方案,就是符合要求的答案了。另外,上述的構造順便還說明了 \(f(n)\leq c(n)\),進而 \(f(n) = c(n)\),不過這已經不重要了。
每次找 \(\text{LIS}\) 是 \(\mathcal{O}(n\log n)\) 的(我用的樹狀數組)。因為 \(c(n)\) 是 \(\mathcal{O}(\sqrt{n})\) 級別的,所以總時間復雜度 \(\mathcal{O}(n\sqrt{n}\log n)\)。
總結:
我們先想方設法,構造出了一個“划分出的合法子序列數”很大的排列,從而確定了 \(f(n)\) 的一個下界 \(c(n)\)。接下來我們通過歸納地構造,使得對任意長度為 \(n\) 的排列,都能划分出不超過 \(c(n)\) 個合法子序列。
構造的過程中,還用到了 \(\text{dilworth}\) 定理的一個推論:如果原序列的 \(\text{LIS}\) 長度為 \(l\),那么它能被划分為 \(l\) 個下降子序列(而且不能再少了)。
歸類:歸納。
難度:難。
CF1261E Not Same
把問題稍微轉化一下,變成要求構造一個 \(n+1\) 行 \(n\) 列的 \(01\) 矩陣,每一列的和給定,並且要求任意兩行互不相同。
記 \(b(i, j)\) 表示答案矩陣第 \(i\) 行第 \(j\) 列上的數。
法一:
首先將所有數從大到小排好序。
對第 \(i\) 個數,相當於要在第 \(i\) 列填 \(a_i\) 個 \(1\)。我們從第 \(i\) 行開始向下走,依次把經過的前 \(a_i\) 個格子填上 \(1\)(如果到底了就返回第 \(1\) 行)。
下面證明這種做法的正確性。
首先,如果我們對排好序的 \(a\) 序列構造出答案后,再把答案的矩陣列按照原順序交換,顯然結果仍是正確的。故以下討論的 \(b\) 矩陣,均為對排好序的 \(a\) 序列構造的答案。
我們要證明,按上述方法構造出的 \(b\) 矩陣,不存在完全相同的兩行。
反證法:考慮兩行 \(i, j\) (\(i < j\)),假設這兩行相同。
簡單分類討論一下:
- 情況一:\(1\leq i < j\leq n\)。
因為 \(a_{i + 1}\leq n\),所以 \(b(i, i + 1) = 0\)。又因為第 \(i\) 行與第 \(j\) 行相同,所以 \(b(j, i + 1) = 0\)。所以 \(a_{i + 1}\leq j - i - 1\)。
如果 \(i + 2\leq j\),考慮第 \(i + 2\) 列。要么 \(b(i,i + 2) = b(j, i + 2) = 1\),要么 \(b(i, i + 2) = b(j, i + 2) = 0\)。如果 \(b(i,i + 2) = b(j, i + 2) = 1\),那么 \(a_{i + 2}\geq j - i + 1\)。然而,由於 \(a_{i + 2}\leq a_{i + 1}\leq j - i - 1\),所以這種情況不存在,所以只可能 \(b(i, i + 2) = b(j, i + 2) = 0\)。所以 \(a_{i + 2}\leq j - i - 2\)。
同理,可以推出,\(\forall k \in[1, j - i]:a_{i + k}\leq j - i - k\)。所以 \(a_{j}\leq 0\)。與題意矛盾。
- 情況二:\(1\leq i\leq n\),\(j = n + 1\)。
因為 \(a_{i}\geq 1\),所以 \(b(i, i) = 1\)。又因為第 \(i\) 行與第 \(j = n + 1\) 行相同,所以 \(b(n + 1, i) = 1\)。所以 \(a_{i}\geq n + 2 - i\geq 2\)。
如果 \(i > 1\),考慮第 \(i - 1\) 列。因為 \(a_{i}\geq 2\),所以 \(a_{i - 1}\geq a_{i}\geq 2\)。所以 \(b(i, i - 1) = 1\)。所以 \(b(n + 1, i - 1) = 1\)。所以 \(a_{i - 1}\geq n + 3 - i\)。
同理,可以推出,\(\forall k \in[0, i - 1]: a_{i - k} \geq n + k + 2 - i\)。所以 \(a_{1} \geq n + 1\),與題意矛盾。
順便一提,我們原本的想法是把 \(a\) 序列按從小到大排序。這樣也能得到一個“似乎正確”的做法。但是該做法可能導致第 \(n\) 行和第 \(n + 1\) 行相同。這給我們的啟示是:
- 在證明時,注意考慮 \(i\leq n\),\(j = n + 1\) 的特殊情況,是至關重要的。
- 如果從小到大排序不行,可以嘗試反過來。
法二:
不需要排序。
我們從 \(1\) 到 \(n\),依次考慮每一列。
假設當前考慮到底 \(i\) 列,我們看看前 \(i - 1\) 列里(\((n + 1)\times(i - 1)\) 的矩陣里)有哪些相同的行(初始時所有行都是相同的)。
- 如果沒有相同的行,那么后面無論怎么填,都不會再出現相同的行。所以可以隨便填。
- 否則找出相同的兩行 \(r_1, r_2\),令 \(b(r_1, i) = 1\),\(b(r_2, i) = 0\)。剩下的 \(a_i - 1\) 個 \(1\) 隨便填。
下面證明這種做法的正確性。
把相同的行視為一組,記錄每組的大小,得到一個可重集。
例如,初始時所有行都是相同的,那么可重集就是 \(\{n + 1\}\)。加入了 \(a_1\) 后,可重集變成:\(\{a_1, n + 1 - a_1\}\)。
如果所有行都不同,那么可重集是 \(\{1, 1, \dots, 1\}\)(\(n + 1\) 個 \(1\))。
否則我們每次操作的效果,相當於至少會拆掉可重集里的一個 \(> 1\) 的數。即,從可重集里選擇一個 \(x > 1\),刪掉它,再加入兩個數 \(x_1, x_2\),滿足 \(x_1 + x_2 = x\),\(x_1, x_2 > 0\)。
原可重集 \(\{n + 1\}\),在變為 \(\{1, 1, \dots, 1\}\) 前,最多可以被操作 \(n\) 次。
所以經過 \(n\) 次操作后,可重集一定會變為 \(\{1, 1, \dots, 1\}\),即所有行都不相同。
朴素實現是 \(\mathcal{O}(n^4)\) 的(即每次暴力枚舉兩行,再暴力判斷它們是否相同)。
對行哈希,用哈希來判斷是否相同,可以做到 \(\mathcal{O}(n^2\log n)\) 或者 \(\mathcal{O}(n^2)\)。
總結:
第一種方法很牛逼,不知道怎么想到的,可能需要大量的嘗試。第二種方法應該更容易想到。
難度:中。
Part3 jly 論文
注:本部分所有內容均參考了 jly 的論文。如有雷同,我抄他的。
知識點:鴿巢原理
鴿巢原理,或稱為抽屜原理,是組合數學中一個非常重要的原理。通常的表述是,若將 \(n\) 件物品放入 \(k\) 個抽屜,則其中一定有一個抽屜包含至少 \(\lceil\frac{n}{k}\rceil\) 件物品,也一定有一個抽屜包含至多 \(\lfloor\frac{n}{k}\rfloor\) 件物品。
在一些構造題中,常常會要求構造一個權值至少為(或不超過)某一個數的方案。很多時候,可以考慮找出若干個可行的方案,使得它們的權值之和是定值。假設找出了 \(k\) 個可行 方案,其總權值和為 \(n\),由抽屜原理,這些方案中最小的權值一定不超過 \(\lfloor\frac{n}{k}\rfloor\),最大的權值至少為 \(\lceil\frac{n}{k}\rceil\)。
CF1450C2 Errich-Tac-Toe (Hard Version)
記第 \(r\) 行第 \(c\) 列的格子為 \((r, c)\)。
將所有格子,按照 \((r + c)\bmod 3\) 的值,分為 \(0, 1, 2\) 三類。發現一個很好的性質:任意一行或一列上連續的三個格子,一定恰好包含 \(0, 1, 2\) 類格子各一個。
考慮通過操作,使得 \(0, 1, 2\) 三類里,有一類格子上全是 \(\text{X}\),有一類格子上全是 \(\text{O}\)(如果格子非空的話),這樣就一定滿足題意了。
具體來說,我們有如下六種操作方案(以下討論的均是非空的格子):
- 把第 \(0\) 類格子全改成 \(\text{X}\),把第 \(1\) 類格子全改成 \(\text{O}\)。
- 把第 \(1\) 類格子全改成 \(\text{X}\),把第 \(0\) 類格子全改成 \(\text{O}\)。
- 把第 \(0\) 類格子全改成 \(\text{X}\),把第 \(2\) 類格子全改成 \(\text{O}\)。
- 把第 \(2\) 類格子全改成 \(\text{X}\),把第 \(0\) 類格子全改成 \(\text{O}\)。
- 把第 \(1\) 類格子全改成 \(\text{X}\),把第 \(2\) 類格子全改成 \(\text{O}\)。
- 把第 \(2\) 類格子全改成 \(\text{X}\),把第 \(1\) 類格子全改成 \(\text{O}\)。
考慮初始時,每種格子上,每種字母的出現次數,如下表:
那么,六種方案所需的操作次數分別是:
- \(o_0 + x_1\)
- \(o_1 + x_0\)
- \(o_0 + x_2\)
- \(o_2 + x_0\)
- \(o_1 + x_2\)
- \(o_2 + x_1\)
它們的總和是 \(2(x_0 + x_1 + x_2 + o_0 + o_1 + o_2) = 2k\)(\(k\) 的定義見題面)。
根據鴿巢原理,\(6\) 個數的總和為 \(2k\),必存在至少一個數小於等於 \(\lfloor\frac{2k}{6}\rfloor = \lfloor\frac{k}{3}\rfloor\)。也就是說,我們取其中操作次數最少的一種方案,一定是符合題意的。
另外,如果只考慮第 \(2, 3, 6\) 種方案,或只考慮第 \(1, 4, 5\) 種方案,它們的和都是 \(k\),所以最小值必不超過 \(\lfloor\frac{k}{3}\rfloor\)。跟考慮全部的 \(6\) 種方案本質是一樣的。
時間復雜度 \(\mathcal{O}(n^2)\)。
總結:
看到矩陣上連續的 \(m\) 個位置,就想到把矩陣按 \((r + c)\bmod m\) 染色,這是比較套路的。尤其是處理“矩陣上相鄰兩個位置”怎么怎么樣時,我們會把矩陣按 \((r + c)\bmod 2\) 染色。在本題里,我們把矩陣按 \((r + c)\bmod 3\) 染色,這樣帶來的好處是:任意一組“連續的 \(3\) 個位置”,都會包含恰好每類格子各一個。
之后還需要熟練使用鴿巢原理。
歸類:矩陣染色,鴿巢原理。
難度:易。
gym102900B Mine Sweeper II
稱只含數字的 \(0,1\) 的矩陣為 \(01\) 矩陣。本文里,矩陣的行、列均從 \(1\) 開始編號。\(A_{i,j}\) 表示矩陣 \(A\) 第 \(i\) 行第 \(j\) 列上的數值。
可以用一個 \(01\) 矩陣 \(A\) 來描述一張地圖。\(A_{i, j} = 0\) 當且僅當地圖第 \(i\) 行第 \(j\) 列的位置上為空地,\(A_{i,j} = 1\) 當且僅當地圖的第 \(i\) 行第 \(j\) 列的位置上為地雷。
設 \(S(A)\) 表示 \(A\) 所對應的地圖里“所有空地上的數字之和”。形式化地:
設 \(\mathrm{dis}(A, B)\) 表示 \(A, B\) 兩矩陣里不同的位置數。形式化地:
原問題相當於,給定 \(01\) 矩陣 \(A, B\),要求構造一個 \(01\) 矩陣 \(C\),滿足:
引理:一張地圖所有空地上的數字之和等於相鄰的 \(\textbf{(空地, 地雷)}\) 的對數。
形式化地說,定義函數 \(f(r_1, c_1, r_2, c_2)\) 表示 \((r_1, c_1)\) 和 \((r_2, c_2)\) 這兩個位置是否相鄰。那么:
根據上述引理,可以有一個推論:如果將一張地圖的所有地雷改成空地,所有空地改成地雷,其所有空地上的數字和不變。形式化地:
定義 \(T(A)\) 為把矩陣 \(A\) 所有位置上的值分別取反得到的新矩陣。即:
則:
例如,在下圖中,左右張地圖所有空地上的數字之和相等。
上述推論啟發我們,如果令 \(C = B\) 或令 \(C = T(B)\),都滿足 \(S(C) = S(B)\) 的條件。接下來分析第二個條件。
注意到,對於任意兩個大小為 \(n\times m\) 的矩陣 \(A, B\),顯然有:
根據鴿巢原理,可知:
於是我們令 \(C = B\) 或 \(C = T(B)\),必有至少一種方案是合法的。
時間復雜度 \(\mathcal{O}(nm)\)。
代碼略。
總結:
首先需要發現一個核心性質,即:一張地圖所有空地上的數字之和等於相鄰的 \(\text{(空地, 地雷)}\) 的對數。然后可以進一步分析出:如果將一張地圖的所有地雷改成空地,所有空地改成地雷,其所有空地上的數字和不變。據此可以想到兩種構造方案,即 \(C = B\) 或 \(C = T(B)\)。然后用鴿巢原理來分析這兩種方案即可。
在構造題里,如果題目要求用不超過 \(\lfloor\frac{s}{k}\rfloor\) 次操作,完成一個任務,我們考慮使用鴿巢原理,做法是:找出 \(k\) 種構造方案,它們的操作次數之和為 \(s\)。如果存在這樣的 \(k\) 種方案,那么其中必有至少一種方案,操作次數是不超過 \(\lfloor\frac{s}{k}\rfloor\) 的。
歸類:鴿巢原理。
難度:易。
知識點:DFS 樹
在解決一些圖上的構造問題時,DFS 樹往往有非常大的幫助。
一張圖的 DFS 樹是在對其進行深度優先遍歷時,所形成的樹結構。建立了 DFS 樹后, 圖上的邊可以分成四類:
- 樹邊:即每個點到其所有孩子結點的邊,也即每個點第一次被訪問時經過的邊。
- 前向邊:是每個點到其后代的邊,不包括樹邊。
- 后向邊:是每個點到其祖先的邊。
- 其余邊稱為橫叉邊。
其中,前向邊、后向邊、橫叉邊統稱為非樹邊。
在構造題中,通常我們用到的是無向圖的 DFS 樹。如果我們將每條邊按照第一次經過時的方向進行定向,則無向圖的 DFS 樹滿足所有非樹邊都是后向邊。這個性質在解題過程中有非常大的作用。
CF1364D Ehab's Last Corollary
見這篇文章。里面同時包含了多道相關的題目,供讀者參考。
LOJ3176 「IOI2019」景點划分
對三個點集的大小,不妨設 \(a\leq b\leq c\)。
稱一個“連通”的節點集合為“連通塊”(關於“連通”的定義可以見題面)。那么問題相當於,在圖上找出兩個連通塊,使得它們交為空,且大小分別為 \(a, b\) 或 \(a, c\) 或 \(b, c\)。
對於任意點數 $ > 1$ 的連通塊,我們總是可以刪去一個節點,使得剩下的節點仍然連通(找出任意一棵生成樹,刪掉一個葉子即可)。也就是說,我們總是可以把大的連通塊變小。因此,如果存在一種方案,划分出了大小為 \(a\) 和 \(c\) 的連通塊,那么也必存在方案能划分出大小為 \(a\) 和 \(b\) 的連通塊。\(b, c\) 同理。於是我們只需要考慮,如何划分出兩個大小分別為 \(a, b\) 的連通塊即可。
考慮圖是一棵樹的情況。顯然,此時有解的充分必要條件是:存在一條邊,使得邊兩側的子樹大小分別不小於 \(a\) 和 \(b\)。可以枚舉每條邊並判斷。
雖然樹上的問題已經解決了,但為了給一般圖上的做法做鋪墊,我們進一步挖掘此時的性質。給出樹的重心的定義及其基本性質:
\(\mathrm{son}_r(u)\) 表示以 \(r\) 為根時 \(u\) 的兒子的集合。定義 \(\mathrm{size}_r(u)\) 表示以 \(r\) 為根時,\(u\) 的子樹大小。定義 \(f(u) = \max_{v\in\mathrm{son}_u(u)}\{\mathrm{size}_u(v)\}\),即以 \(u\) 為根時,\(u\) 的最大的兒子子樹的大小。取 \(f(u)\) 最大的節點 \(u\),即為樹的重心。如有多個(最多只有兩個),可取任意一個。
性質:如果 \(u\) 是樹的重心,那么 \(\forall v \in\mathrm{son}_u(u)\),\(\mathrm{size}_u(v)\leq \lfloor\frac{n}{2}\rfloor\)。
考慮樹的重心 \(c\)。發現有解的充分必要條件是,以 \(c\) 為根時,存在一個 \(c\) 的兒子子樹大小 \(\geq a\)。也就是說:只需要枚舉和 \(c\) 相連的邊即可!
證明:
充分性:因為 \(b\leq c\) 且 \(b + c = n - a \leq n\),所以 \(b\leq \lfloor\frac{n}{2}\rfloor\)。又因為重心的所有兒子子樹大小 \(\leq \lfloor\frac{n}{2}\rfloor\),所以刪去那個大小 \(\geq a\) 的兒子子樹后,剩余部分的大小 \(\geq n - \lfloor\frac{n}{2}\rfloor \geq b\)。
必要性:如果所有子樹的大小都 \(< a\),意味着任意一個大小為 \(a\) 的連通塊和大小為 \(b\) 的連通塊,都必須包含重心。所以無法滿足兩連通塊交為空這一條件,故無解。
回到一般圖上的情況。建出 DFS 樹。找到 DFS 樹的重心 \(c\)。記 DFS 樹上 \(c\) 上方的部分(除 \(c\) 子樹外的部分)為 \(T\),\(c\) 的兒子子樹分別為:\(S_1, S_2, \dots, S_k\)。考慮幾種情況:
- 如果 \(T\) 或某個 \(S_i\) 的大小 \(\geq a\),那么和樹的情況是一樣的,可以構造出解。
- 如果 \(T\) 和所有 \(S_i\) 的大小都 \(< a\)。就需要考慮無向圖 DFS 樹的性質:不存在橫叉邊。所以不同的 \(S_i\) 之間是沒有連邊的。同時,可能有一些 \(S_i\) 會與 \(T\) 相連。
- 如果所有與 \(T\) 相連的 \(S_i\) 加上 \(T\) 的大小之和 \(< a\),那么一定無解:因為任意一個大小為 \(a\) 的連通塊和大小為 \(b\) 的連通塊,都必須包含重心,所以無法滿足兩連通塊交為空這一條件。
- 否則考慮一個點集 \(X\),初始時為空。先把 \(T\) 加入 \(X\)。然后依次把和 \(T\) 相連的 \(S_i\) 加入 \(X\)(可以以任意順序),直到 \(X\) 的大小不小於 \(a\) 為止。因為所有 \(S_i\) 大小都 \(< a\),所以最終 \(X\) 的大小一定 \(< 2a\)。又因為 \(2a + b\leq a + b + c = n\),所以在刪除 \(X\) 后,剩余節點數量 \(\geq n - 2a\geq b\)。此時,我們一定能在 \(X\) 里構造出連通塊 \(A\),在剩余節點里構造出連通塊 \(B\)。做法比較簡單:
- 先將 \(T\) 加入 \(A\),然后在 \(X\) 中的每個 \(S_i\) 里,找一個與 \(T\) 相連的點加入 \(A\)(記為這個點為 \(u_i\))。把所有 \(u_i\) 加入后,如果大小還是不夠,再將每個 \(S_i\) 里的其他點加入,為了保證聯通,可以從每個 \(u_i\) 開始,在子樹里 DFS。
- 對於 \(B\),先將重心 \(c\) 加入 \(B\),然后把不在 \(X\) 里的子樹依次加入即可。同樣,為了保證聯通,可以從子樹的根開始 DFS。
綜上所述,我們證明了一種情況的無解性,並對除此之外的情況給出了構造方案。
時間復雜度 \(\mathcal{O}(n + m)\)。
總結:
從圖是一棵樹的特殊情況入手思考,發現和重心相關的性質。然后把思路遷移到 DFS 樹中。在本題里,重心的性質帶來的好處是:如果一個子樹大小 \(\geq a\),那么另一側一定能構造出 \(b\)。然后只需要思考所有子樹都 \(< a\) 的情況即可。
歸類:DFS樹。
難度:難。
