<style type="text/css"> .slides { text-align: left !important; } </style> # 二分搜尋 Author: PixelCat --- ## 問題敘述 給你一個只有01的序列,0只會出現在1前面 序列長度是 $N$,第 $i$ 格是 $f(i)$ ---- 像是 ![](https://i.imgur.com/0ktQoxE.png) 或者 ![](https://i.imgur.com/fgkWd6N.png) 甚至 ![](https://i.imgur.com/quF2853.png) ---- 我們不知道這個序列長怎樣 但我們可以多次選一個位置 $i$ 詢問:「$f(i)$ 是1嗎?」 我們希望用最少的詢問找出第一個1的位置 --- ## 暴力出奇蹟 ---- 從第一格開始往後問 哪天終於問出1就找到答案了 簡單,直覺,有效 ---- 最差情況下假如最後一格才出現1 總共要詢問 $N$ 次 ---- 這種辦法小學生都會 還可以更好嗎? 不行的話我就不會寫這份投影片了 --- ## 改進 ---- 為了改進我們需要一點觀察與偷懶 > 0只會出現在1前面 剛剛好像沒用到這個性質 有他就可以偷懶了 ---- 你今天什麼都還不知道 丟兩顆骰子決定詢問這一格 ![](https://i.imgur.com/AN0c3sF.png) ---- 假如你發現他是1 ![](https://i.imgur.com/xHqG0Jh.png) 後面還會出現0嗎? 不可能 ![](https://i.imgur.com/UNTgyGV.png) 那他們自然不會是答案,可以不理他了 ![](https://i.imgur.com/8BvfgHG.png) ---- 假如你發現他是0 ![](https://i.imgur.com/ojZuhn2.png) 前面還會出現1嗎? 不可能 ![](https://i.imgur.com/oYqdI9J.png) 那他們自然不會是答案,可以不理他了 ![](https://i.imgur.com/KoFDFU2.png) ---- ### 二分搜! 假如我們確定答案在 $[L,R]$ 區間裡面 找到正中間 $m=\left \lfloor{\frac{L+R}{2}}\right \rfloor$,詢問 $f(m)$ - 假如 $f(m)=1$,答案一定在 $[L,m]$ 裡面 - 假如 $f(m)=0$,答案一定在 $[m+1,R]$ 裡面 一直到最後 $L=R$,答案就是他了 ---- 為何正中間? 假如選正中間,一定可以刪掉一半 假如不選正中間,可能刪掉不到一半 ---- 總共要問幾次? 一開始「答案區間」的長度 $R-L+1=N$ 每問一次,答案區間變短一半 最後 $R-L+1=1$ $$ N\times\left(\frac{1}{2}\right)^x=1\\ x=\log_2N $$ 就算 $N=1024$,問個10次就完事了 ---- ### 示意code ```cpp= int lo = 1, hi = N; while(lo != hi){ int m = (hi + lo) / 2; if(f(m) == 1) hi = m; else lo = m + 1; } cout << lo << "\n"; ``` --- ## 一定要是序列嗎 ---- 給你一個函數,吃一個實數,吐0或1回來 0依然不會出現在1後面 $$ f:\mathbb{R}\rightarrow \left\{0,1\right\}\\ x_1\leq x_2\Longrightarrow f(x_1)\leq f(x_2) $$ 請問分界點在哪裡? 一開始確定答案在 $[0,C]$ 裡 輸出跟正確答案的差異在 $\epsilon=10^{-9}$ 以內都算正確 ---- 跟剛剛相同的想法 假如某個地方是$1$,那後面全部都會是$1$ 假如某個地方是$0$,那前面全部都會是$0$ 還是可以偷懶! ---- 假如現在答案可能出現在 $[L,R]$ 之間,那就戳 $m=\frac{L+R}{2}$ 假如$f(m)=1$,那答案一定在$[L,m]$裡 假如$f(m)=0$,那答案一定在$[m,R]$裡 ---- ### 複雜度 每次切一半,一開始$R-L=C$,切到 $R-L\leq \epsilon$ 為止 $$ C\times(\frac{1}{2})^x\leq \epsilon \\ x\geq\log{\frac{C}{\epsilon}} $$ 複雜度$\mathcal{O}(\log{\frac{C}{\epsilon}})$ ---- ### 實作 ```cpp= double lo = 0, hi = C; double eps = 1e-9; while(hi - lo > eps){ double m = (hi + lo) / 2; if(f(m)) hi = m; else lo = m; } cout << lo << "\n"; ``` ---- ### 蛤? 可是 ![](https://i.imgur.com/GQypJBm.png) ---- 精度爆了 怎麼辦? 改判斷條件 ---- 搜幾輪取決於題目給的範圍和時限 ~~二分搜搜幾輪才會TLE~~ ```cpp= double lo = 0, hi = C; for(int R = 0;R < 100;R++){ //搜100輪 double m = (hi + lo) / 2; if(f(m)) hi = m; else lo = m; } cout << lo << "\n"; ``` --- ### 那... 大多數問題都不會直接給你這個序列(函數)怎麼辦? ---- ### 所以 很多問題要你求一個最大(小)的答案滿足某個條件 而且 1. 直接做不好做 2. 給一個可能的答案可以很方便的驗證行不行 3. 有單調性 搜他! --- ## 經典到發臭的經典題 [zerojudge c575 基地台](https://zerojudge.tw/ShowProblem?problemid=c575) 某數線上有 $N$ 個服務點,座標在 $x_1, x_2, \dots x_N$,你可以任選位置蓋 $K$ 個基地台來服務這些服務點,每個基地台分別可以覆蓋距離他們 $R$ 以內的所有服務點。 請問:$2R$ 最小可以是多少? $1\leq K<N\leq 5\times 10^4$ $\left|x_i\right|\leq 10^9$ ---- ![](https://i.imgur.com/ZyfwOHY.png =450x) $K=1$ ,最小的 $2R$ 是7,基地台在 $x'_1=4.5$ 處 $K=2$ ,最小的 $2R$ 是3,基地台在 $x'_1=2,\,x'_2=6.5$ 處 $K=3$ ,最小的 $2R$ 是1 ---- 直接做要怎麼做? 我不會 ;w; ---- 給一個答案(半徑)可以驗證嗎? 從左邊掃到右邊,滿足每個服務點都被蓋到的同時盡可能把基地台設在越右邊越好,不虧 可以 $\mathcal{O}(N)$ 驗證! ---- 滿足二分搜條件嗎? 假如這個半徑可以,那半徑變大一定也可以 假如這個半徑不行,那半徑變小一定更不行 條件滿足了 ---- 搜他 $\mathcal{O}(N\log{10^9})$ --- ## C++內建二分搜 ---- 假如是要在排序好的陣列中找一個值 有內建的函數可以做到 複雜度是 $\mathcal{O}(\log($陣列長度$))$ ---- ```cpp= vector<int>v{3,5,6,7}; x=3; p=lower_bound(v.begin(),v.end(),x)-v.begin(); cout<<p<<'\n';// 0 p=upper_bound(v.begin(),v.end(),x)-v.begin(); cout<<p<<'\n';// 1 ``` --- ## 例題 codeforces->problemset->binarysearch [CSES-Array Division](https://cses.fi/problemset/task/1085) [CSES-Factory Machines](https://cses.fi/problemset/task/1620) [CSES-Multiplication Table](https://cses.fi/problemset/task/2422) --- ## 以上 ---- 二分搜概念簡單 但稍作修改包裝就可以變出各種妖魔鬼怪 許多進階的主題也都會用到 例如:線段樹、LCA等 ~~真是陰魂不散~~ ---- 所以要學好二分搜喔~
{"metaMigratedAt":"2023-06-15T22:22:29.063Z","metaMigratedFrom":"YAML","title":"二分搜","breaks":true,"slideOptions":"{\"theme\":\"black\",\"transition\":\"slide\"}","description":"Author: PixelCat","contributors":"[{\"id\":\"25d6f561-7fc8-4c0b-8638-c8ad8a1c8b75\",\"add\":527,\"del\":574},{\"id\":\"84d8099a-a721-4db6-8fe4-cfea2a2d82b4\",\"add\":5138,\"del\":1640},{\"id\":\"b6728096-4c9a-4b93-9b51-70c56850e20f\",\"add\":634,\"del\":29}]"}
    914 views
   Owned this note