固定一個區間 \([l,r]\)
利用 \(mid = (l+r)/2\) 的結果
決定要如何移動 \(l,r\)
猜數字
我想一個介於 \([1,100]\) 的數字 \(x\)
每次猜測後,你會知道這個數字太大或太小
想辦法猜到這個數字
這些東西都在 STL 當中有內建
int x;
cin >> x;
int l = 0, r = n-1;
while(l < r){
int mid = (l+r)/2;
if(arr[mid] > x) r = mid-1;
else if(arr[mid] < x) l = mid+1;
else{
cout << mid << "\n";
}
}
2. 在一個排序好的陣列中尋找 \(\ge x\) 的第一個數字
int x;
cin >> x;
int l = 0, r = n;
while(l < r){
int mid = (l+r)/2;
if(arr[mid] >= x) r = mid;
else l = mid+1;
}
if(r==n){
cout << -1 << "\n";
}else{
cout << r << "\n";
}
3. 在一個排序好的陣列中尋找 \(> x\) 的第一個數字
int x;
cin >> x;
int l = 0, r = n;
while(l < r){
int mid = (l+r)/2;
if(arr[mid] > x) r = mid;
else l = mid+1;
}
if(r==n){
cout << -1 << "\n";
}else{
cout << r << "\n";
}
寫法如下
int l = 0, r = n-1;
while(l < r){
int mid = (l+r+1)/2;
if(check(mid)) l = mid;
else r = mid-1;
}
cout << l << "\n";
從 \(l\) 開始,利用位元運算尋找 \(r-l\) 的距離
int l = 0, r = n-1;
for(int i = LOGN; i >= 0; i--){
//1<<i 表示將 1 左移 i 位,等價於 2^i
if(l+(1<<i) < n && arr[l+(1<<i)] <= x){
l += (1<<i);
}
}
if(arr[l]==x) cout << l << "\n";
else cout << -1 << "\n";
優點: 不用在意如 \(r-l=1\) 時的細節
前面那些都可以直接使用 STL 的函數
那我們為什麼要學二分搜呢?
在一個型如 \(\{0,0,\cdots,0,1,\cdots,1,1\}\) 的陣列中尋找 \(0,1\) 交界
你可能會覺得這就是剛剛講過的 lower_bound(0)
就可以解決的問題 那有什麼特別的點
如果把 \(1\) 想成 true
然後把 \(0\) 想成 false
當題目問的是以下幾種問題時,可以試著轉成二分搜思考
我們來看幾個例題
我們無法精確找到確切的那個值
題目會要求誤差在 \(10^{-6}, 10^{-7}, 10^{-8}\) 以內
定一個 \(\epsilon\) (EPS) 為一個很小的數字 \((10^{-7}, 10^{-8})\)
然後當 \(r-l \le \epsilon\) 的時候跳出迴圈
const double EPS = 1e-7;
double l = 0, r = n;
while(r-l <= EPS){
double mid = (l+r)/2;
if(check(mid)) r = mid;
else l = mid;
}
直接執行夠多的次數 \(100\) 次等等
答案就夠精確了
double l = 0, r = n;
for(int i = 0;i < 100;i++){
double mid = (l+r)/2;
if(check(mid)) r = mid;
else l = mid;
}
我們也可以二分搜!
如果我們有一個數字 \(x\)
要怎麼檢查
如果我們有一個數字 \(x\)
要怎麼檢查
轉換式子
如果我們有一個數字 \(x\)
要怎麼檢查
轉換式子