Chapter III. 漸入佳境
基礎演算法
對答案二分搜
作者WiwiHo
協作者rabhunter

先直接看例題:

例題
牆上海報

有一個由 $n$ 個木板組成的柵攔,每個木板的高度為 $h[1],h[2],\dots,h[n]$,有 $k$ 張海報要張貼在柵欄上,每張海報的寬度為 $w[1],w[2],\dots,w[n]$ 並且高度均為 $1$。

若要張貼海報在高度為 $x$ 的高度,則第 $i$ 張海報需要張貼在一個長度為 $w[i]$ 的連續並且高度都不小於 $x$ 的木板上,且每張海報張貼的高度需要一致、按照順序並不能重疊 (可以相連)。詢問最高可以貼到多高的位置。

條件限制
  • $1 \leq n \leq 2 \times 10^5$
  • $1 \leq k \leq 5000$
  • $1 \leq h_i \leq 10^9$
  • $\sum w_i \leq n$

既然題目要求所有海報都要貼在同一個高度上,一個很暴力的想法就是直接枚舉所有可能的高度 $x$,看看有沒有辦法把所有海報都貼在這個高度。在高度已經固定的前提下,剩下的部分就很簡單了,只要從最左邊的木板開始往右看,找到第一個可以貼上第一個海報的位置,把它貼上去、再往右繼續找,找到可以貼上第二個海報的位置、……,看看最後能不能把所有海報貼完就好了,好好實作就可以在 $O(n)$ 的時間解決一個 $x$。

然而,木板的高度最高高達 $10^9$!即便木板的高度只有 $n$ 種,所以其實只要枚舉「被用到的最高木板」的高度就夠了,這樣也得要花上 $O(n^2)$ 的時間,實在是太慢了。

藏起來的單調性


既然線性搜尋 $x$ 不行,那可以用二分搜尋法嗎?

如果要用二分搜尋法的話,我們就要想辦法找出 $x$ 的「單調之處」。先回去想想枚舉 $x$ 的過程,如果是從 $1$ 開始往上枚舉的話,其實在遇到第一個貼不完的高度時,就可以停下來了,畢竟高度越高的時候,能貼的地方只會越來越少,因此在更高的高度,也是不可能貼完的。當高度越高,用上面的方法能貼的海報就會越來越少、當高度越低,能貼的海報就越來越多。

這樣一來,我們就有一個「藏起來的序列」,這個序列的第 $x$ 項就是貼在高度 $x$ 的時候,最多可以貼幾張海報,長度就是我們本來的枚舉上界 $C=10^9$。而這個序列是非嚴格遞減的,我們的目標是找到最後一個 $n$ 的位置,直接套一層二分搜就可以在 $O(n \log C)$ 的時間解決這個問題了。

動動腦:可不可以做到 $O(n \log n)$ 呢?

更一般的想法


等等!我們的序列真的有需要長得這麼複雜嗎?我們真的在意「能貼幾張海報」這件事情嗎?雖然用上面的作法時,能貼幾張總是會知道的,不過在二分搜的時候,我們真正在乎的事情是「可不可以貼完」,目標是找到最後一個「可以」的 $x$,也就是說序列的數字根本只分成 $=n$ 和 $<n$ 兩種,$<n$ 的數字實際如何不重要。因此,我們想像中要搜尋的那個序列,其實不必要是具體有意義的數字,只要簡單地想像那個序列其實是長成「是是是……是是否否否……否否否」的樣子就好了。

在實作的時候,可以寫一個獨立的 function,輸入 $x$、回傳「這個 $x$ 能不能是答案」,千萬不要先硬是把整個序列建出來,不然就跟直接枚舉一樣了。

bool check(int x) {
    // return true 代表可以把所有海報貼在高度 x
    // return false 代表不行
}

int solve() {
    // 現在的答案可能範圍是 [l, r)
    // 答案可以是 10^9!所以 r 要是 10^9+1
    int l = 1, r = 1'000'000'001;
    while (l + 1 < r) { 
        int mid = (l + r) / 2;
        if (check(mid)) l = mid; // mid 可以貼,正確答案只會 >= mid
        else r = mid; // mid 不能貼,正確答案只能 < mid
    }
    return l;
}

像這種「二分搜要作為答案的數字,看看它可不可以達成題目要求」的方法,就被稱作對答案二分搜。當題目本身不太好被直接解決,但如果問題從「找出最佳的滿足題目要求的 $x$」被改成「某個 $x$ 是否滿足題目要求」的時候就可以解決,再加上有「可以的 $x$ 和不可以的 $x$ 存在一個分界線」(在最大化答案的問題就是形如「是是是是否否否否」,在最小化答案的問題就是形如「否否否否是是是是」)的性質時,就可以使用對答案二分搜。

在對答案二分搜的題目中,對答案二分搜這件事本身往往只是題目最外層的包裝,拆掉這層皮以後通常還得搭配其他的技巧,就像剛才的例題一樣,「不斷找到第一個可以貼海報的位置來貼」其實有著貪心的概念,貪心法的題目就經常外加一層對答案二分搜的包裝。

習題


習題
基地台

有一條數線上有 $N$ 個服務點,第 $i$ 個服務點的座標在 $P[i]$,並且你可以選擇最多 $K$ 個位置(可以在任意位置,也不一定要整數座標)設置基地台。你的目標是決定一個基地台的服務半徑 $R$,滿足有辦法設置基地台,使得每個服務點距離 $\leq R$ 以內都至少有一個基地台。求最小的 $R$。

條件限制
  • $1 \leq K < N \leq 50000$
  • $0 \leq P[i] \leq 10^9$
習題
人各有志

給 $N$ 個整數 $L_1,L_2,\dots,L_N$,求兩兩數字的差的絕對值(共有 $\frac{N(N-1)}{2}$ 個,重複幾次就算幾個)中第 $K$ 大的。

條件限制
  • $1 \leq N \leq 2^{15}$
  • $1 \leq K \leq \frac{N(N-1)}{2}$
  • $0 \leq L_i \leq 2^{30}$
習題
裁員風暴

給 $n$ 個數 $w_1,w_2,\dots,w_n$,求所有挑兩個數(可重複,有 $\frac{n(n+1)}{2}$ 種選法)的方法之中,第 $k$ 大的選中的數的平均值(重複幾次就算幾個)。

條件限制
  • $1 \leq n \leq 2 \times 10^5$
  • $1 \leq k \leq \frac{n(n+1)}{2}$
  • $-10^9 \leq w_i \leq 10^9$
習題
希爾伯特的房客
Source:TIOJ 1598

有一間旅館裡面有無限個房客,其中的前 $n$ 個房客每個人都拿到了一些鬆餅,第 $i$ 個房客有 $P_i$ 個鬆餅。一個房客可以花一分鐘做以下其中一件事:

  • 吃掉一個他自己擁有的鬆餅。
  • 把自己擁有的一些鬆餅(可以很多個)送給某位另一個房客(不一定要是前 $n$ 位房客,但一次只能送給同一個人)。

所有房客做第二種事情的次數總和最多只能有 $B$ 次。求至少幾分鐘內能吃完所有鬆餅。(不同的房客可以同時做事。)

條件限制
  • $1 \leq n \leq 10^5$
  • $0 \leq B \leq 10^6$
  • $P_i \leq 10^6$