基本思想
二分搜索法的基本思想是通过不断的缩小解可能存在的范围,从而求得问题最优解的方法。比如一个直观的问题是在一个由小到大的数列a中找到一个数ai,使得ai满足ai>=k的同时,i最小。由于此数列是有序的,所以找到中间位置的数amid和k比较,如果k<=amid,证明k在左半区域,区间左边界变为mid,否则在右半区域,区间右边界变为mid。如此下来,直至区间长度小于1,结束循环,最小的i就是右边界的值。
二分搜索的优势在于时间上的高效,每次去掉一半的搜索区间,可以在O(logn)的时间复杂度求得最终的解。但是这种高效是建立在事先将数排好序的基础上的。
实现原理
反复与区间的中点进行比较,不断的将解的范围缩小到原来的一半,直至满足一定的条件后结束算法。简单起见,直接看一道VJudge上的例题Lower Bound ,具体细节参考如下代码:
1 #include <cstdio> 2 3 const int maxn = 100000 + 10; 4 int a[maxn]; 5 int n, q; 6 7 void query(int k) { 8 int lb = -1, ub = n;//初始化区间短点 9 while(ub - lb > 1) {//结束条件是当区间的长度小于1的时候 10 int mid = (ub + lb) / 2;11 //通过判断缩小区间为原来的一半 12 if(a[mid] >= k)13 ub = mid;14 else15 lb = mid;16 }17 18 printf("%d\n", ub);19 }20 21 int main()22 {23 while(scanf("%d", &n) != EOF) {24 for(int i = 0; i < n; i++) {25 scanf("%d", &a[i]);26 }27 28 scanf("%d", &q);29 while(q--) {30 int k;31 scanf("%d", &k);32 query(k);//查询k 33 }34 }35 return 0;36 }
当然C++中的STL以lower_bound函数的形式实现了二分搜索,直接可以调用。参考代码如下:
1 #include <cstdio> 2 #include <algorithm> 3 #include <vector> 4 using namespace std; 5 6 const int maxn = 100000 + 10; 7 int n, q; 8 vector<int> v; 9 10 int main () {11 while(scanf("%d", &n) != EOF) {12 v.clear();13 for(int i = 0; i < n; i++) {14 int tmp;15 scanf("%d", &tmp);16 v.push_back(tmp);17 }18 19 scanf("%d", &q);20 while(q--) {21 int k;22 scanf("%d", &k);23 24 printf("%d\n", lower_bound(v.begin(), v.end(), k) - v.begin());25 }26 } 27 return 0;28 }
在编程竞赛中的典型应用
二分搜索算法除了在有序数组中查找值之外,在求解最优解的问题上也非常有用。比如求满足某个条件C(x)的最小x,我们可以假定一个区间,取该区间的中点值为mid,如果满足C(mid),左边界等于区间中点,即将范围缩小在右侧(最大化),如果不满足,那么右边界等于区间中点,即将范围缩小在左侧。直到将区间缩小到一定的程度后,在此精度下的问题最优解就求出来了。
1.假定一个解并判断是否可行,例如POJ 1064 Cable master
2.最大化最小值,例如POJ 2456 Aggressive cows
3.最大化平均值,例如POJ 3111 K Best
例题解析
有N条绳子,长度分别是Li,如果从它们中切割出K条长度相同的绳子的话,每条绳子最长是多少
我们将区间设为0到INF(最大),然后尝试区间的中点,向满足条件的最大值逼近(循环100次精度大概是1e-30)。条件C(x):Li/x的总和是否大于K,代码如下:(坑点是最后输出保留两位小数,不进位)
1 #include <cstdio> 2 #include <cmath> 3 const int maxn = 10000 + 10; 4 const int inf = 99999999; 5 6 int N, K; 7 double L[maxn]; 8 9 bool C(double x) {10 int num = 0; 11 for(int i = 0; i < N; i++) {12 num += (int)(L[i] / x);13 }14 return num >= K;15 }16 void solve() {17 double lb = 0, ub = inf;18 for(int i = 0;i < 100; i++) {19 double mid = (ub + lb) / 2;20 if(C(mid))21 lb = mid;22 else 23 ub = mid;24 }25 26 printf("%.2f\n", floor(ub * 100) / 100);27 }28 int main()29 {30 while(scanf("%d%d", &N, &K) != EOF) {31 for(int i = 0; i < N; i++) {32 scanf("%lf", &L[i]);33 }34 35 solve();36 } 37 return 0;38 }
将M头牛放在N个牛舍里,使得这M头牛之间的举例尽可能的大。
意即求解满足C(d)的最大d。而其中的d表示M头牛之间的举例均不小于d。区间的结束条件就是区间长度大于等于1,关键是如何判断满足条件,其实也好判,采用贪心的方法,一次往后使用即可,如果M头牛安排完了,满足,否则不满足。代码如下:
1 #include <cstdio> 2 #include <algorithm> 3 using namespace std; 4 5 const int maxn = 100000 + 10; 6 const int inf = 1000000000 + 10; 7 int N, M; 8 int x[maxn]; 9 10 bool C(int d) {11 int last = 0; 12 for(int i = 1; i < M; i++) {13 int crt = last + 1;14 while(crt < N && x[crt] - x[last] < d) {15 crt++;16 }17 18 if(crt == N) return false;19 last = crt;20 }21 return true;22 }23 void solve() {24 sort(x, x + N);25 26 int lb = 0, ub = inf;27 while(ub - lb > 1) {28 int mid = (ub + lb) / 2;29 if(C(mid))30 lb = mid;31 else32 ub = mid;33 } 34 35 printf("%d\n", lb);36 }37 int main()38 {39 while(scanf("%d%d", &N, &M) != EOF) {40 for(int i = 0; i < N; i++) {41 scanf("%d", &x[i]);42 }43 44 solve();45 }46 return 0;47 }
有n个物品的价值和重量分别是vi和mi,从中选出k件物品使得单位重量的价值最大。
C(x)=((vi - x * wi) 从大到小排列中的前k个的和不小于0)
精度1e-30
1 #include <cstdio> 2 #include <algorithm> 3 using namespace std; 4 5 const int maxn = 100000 + 10; 6 const double inf = 0x3f3f3f3f; 7 const double EPS = 1.0e-6; 8 9 int N, K;10 int v[maxn], w[maxn];11 struct Node {12 double val;13 int id;14 } y[maxn];15 16 bool cmp(struct Node a, struct Node b) {17 return a.val > b.val;18 }19 bool C(double x) {20 for(int i = 0; i < N; i++) {21 y[i].id = i + 1;22 y[i].val = (v[i] - x * w[i]);23 }24 25 sort(y, y + N, cmp);26 double sum = 0;27 for(int i = 0; i < K; i++) {28 sum += y[i].val;29 }30 return sum >= 0;31 }32 void solve() {33 double lb = 0, ub = inf;34 35 while(ub - lb > EPS) {36 double mid = (ub + lb) / 2;37 if(C(mid))38 lb = mid;39 else40 ub = mid;41 } 42 43 for(int i = 0; i < K - 1; i++) {44 printf("%d ", y[i].id);45 }46 printf("%d ", y[K - 1].id);47 }48 49 int main()50 {51 while(scanf("%d%d", &N, &K) != EOF) {52 for(int i = 0; i < N; i++) {53 scanf("%d%d", &v[i], &w[i]);54 }55 56 solve();57 } 58 return 0;59 }
至此,二分搜索算法就讲完了,二分的思想就是一次去一半,寻找满足条件的最优,在解决最优解问题的时候要找好结束条件和满足的条件。算法不难,关键是思考问题和实现算法的过程。
原文出处:https://www.cnblogs.com/wenzhixin/p/9630277.html