二分法是一種隨處可見缺非常精妙的算法,經常能爲我們打開解答問題的突破口。二分的基礎的用法是在單調序列或單調函數中進行查找。因此當問題的答案具有單調性時,就可以通過二分把求解轉化爲判定(根據複雜度理論,判定的難度小於求解),這使得二分的運用範圍變得很廣泛。
據說,只有 10% 的程序員能寫對二分。二分的實現方法多種多樣,但是其細節之處確實需要仔細考慮。對於整數域上的二分,需要注意終止邊界、左右區間取捨時的開閉情況,避免漏掉答案或造成死循環;對於實數域上的二分,一般來說大家熟練掌握自己的一種正確寫法即可。
本篇結尾還爲大家分享了兩種二分算法模板。
題目一:數的範圍 來源:AcWing
給定一個按照升序排列的長度爲n的整數數組,以及 q 個查詢。 對於每個查詢,返回一個元素k的起始位置和終止位置(位置從0開始計數)。
如果數組中不存在該元素,則返回“-1 -1”。
輸入格式
第一行包含整數n和q,表示數組長度和詢問個數。
第二行包含n個整數(均在1~10000範圍內),表示完整數組。
接下來q行,每行包含一個整數k,表示一個詢問元素。
輸出格式
共q行,每行包含兩個整數,表示所求元素的起始位置和終止位置。 如果數組中不存在該元素,則返回“-1 -1”。
數據範圍
1≤n≤100000
1≤q≤10000
1≤k≤10000
AC代碼如下:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
typedef long long LL;
using namespace std;
const int MAXN = 1e6;
int n, q, a[MAXN], x;
int main() {
cin >> n >> q;
for(int i = 0; i < n; i++) {
cin >> a[i];
}
while(q--) {
scanf("%d", &x);
int l = 0, r = n-1, mid;
while(l < r) {
mid = l + r >> 1;
if(a[mid] < x) l = mid + 1;
else r = mid;
}
// 找到左端點
// 判斷是否存在等於x點
if(a[l] != x) {
printf("-1 -1\n");
continue;
}
printf("%d ", l);
// 這裏記得將l r 初始化
l = 0, r = n-1;
while(l < r) {
mid = l + r + 1 >> 1;
if(a[mid] > x) r = mid - 1;
else l = mid;
}
// 找到右端點
printf("%d\n", l);
}
return 0;
}
題目:分巧克力 第八屆藍橋杯題目
兒童節那天有 K 位小朋友到小明家做客。 小明拿出了珍藏的巧克力招待小朋友們。 小明一共有 N 塊巧克力,其中第 i 塊是 Hi×Wi
的方格組成的長方形。 爲了公平起見,小明需要從這 N 塊巧克力中切出 K 塊巧克力分給小朋友們。 切出的巧克力需要滿足:
形狀是正方形,邊長是整數 大小相同 例如一塊 6×5 的巧克力可以切出 6 塊 2×2 的巧克力或者 2 塊 3×3 的巧克力。
當然小朋友們都希望得到的巧克力儘可能大,你能幫小明計算出最大的邊長是多少麼?
輸入格式
第一行包含兩個整數 N 和 K。 以下 N 行每行包含兩個整數 Hi 和 Wi。 輸入保證每位小朋友至少能獲得一塊 1×1 的巧克力。
輸出格式
輸出切出的正方形巧克力最大可能的邊長。
數據範圍
1≤N,K≤105,
1≤Hi,Wi≤105
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
typedef long long LL;
using namespace std;
const int MAXN = 1e5+10;
int n, k, MaxH, MaxW, MaxA;
int h[MAXN], w[MAXN];
int check(int m) {
int ans = 0;
for(int i = 0; i < n; i++) {
ans += (h[i] / m) * (w[i] / m);
// 長和寬能放下的最大長度的乘積
}
return ans;
}
int main() {
cin >> n >> k;
for(int i = 0; i < n; i++) {
scanf("%d %d", &h[i], &w[i]);
}
int l = 1, r = 1e5, mid;
while(l < r) {
mid = l + r + 1 >> 1;
// check條件:注意題目要求答案的最大值
if(check(mid) >= k) l = mid;
else r = mid - 1;
}
// 輸出l或者r,而不是mid
cout << l << endl;
return 0;
}
整數二分查找算法模板:來源 yxc
版本1
當我們將區間[l, r]劃分成[l, mid]和[mid + 1, r]時,其更新操作是r = mid或者l = mid + 1;,計算mid時不需要加1。
C++ 代碼模板:
int bsearch_1(int l, int r)
{
while (l < r)
{
int mid = l + r >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
return l;
}
版本2
當我們將區間[l, r]劃分成[l, mid - 1]和[mid, r]時,其更新操作是r = mid - 1或者l = mid;,此時爲了防止死循環,計算mid時需要加1。
C++ 代碼模板:
int bsearch_2(int l, int r)
{
while (l < r)
{
int mid = l + r + 1 >> 1;
if (check(mid)) l = mid;
else r = mid - 1;
}
return l;
}
這個模板的來源是,因爲整數除法是向下取整的,當區間只剩下兩個數時,l 和 r 相等,我們看第二個模板,如果此時滿足 if 條件,那 mid = (l + r) / 2 = (r - 1 + r) / 2,向下取整之後 mid 還是等於 l,那麼就會進入死循環。所以有了
int mid = l + r + 1 >> 1;