由九度1502引出的对二分查找的一点总结v1.0

做了九度1502后,发现自己对二分查找越来越不理解了,特别是各种边界情况,left = mid还是left = mid + 1 ? right  = mid 还是 left = mid-1 ? return left ,return mid还是return right?仔细研究后发现这里面大有玄机。于是写下一点点心得体会,免得以后忘记了。

在刘汝佳大神的《算法竞赛入门经典》中的二分查找是这样的

int bsearch(int* A, int x, int y, int v)
{
    while(x < y)
    {
        m = x + (y-x)/2;
    	if(A[m] == v)  //A[]是待查找数组, v是要查找的值
	    return m;
    	else if(A[m] > v)
	    y = m;
        else
            x = m+1;
    }
	return -1;
}


一开始并没有太深入探究为什么要这样写(每次如何更新x和y的值,最后返回的值等等),只是大概知道二分的工作方式就是每次缩减一般的搜索范围,直到最后查找的范围很小。

但是1502令我wa了差不多二十多三十次之后,我似乎理解了为什么要这样写(但貌似错的地方不是这一部分23333).

先贴上1502的题目描述:

题目1502:最大值最小化

时间限制:1 秒

内存限制:128 兆

特殊判题:

提交:533

解决:197

题目描述:

在印刷术发明之前,复制一本书是一个很困难的工作,工作量很大,而且需要大家的积极配合来抄写一本书,团队合作能力很重要。
当时都是通过招募抄写员来进行书本的录入和复制工作的, 假设现在要抄写m本书,编号为1,2,3...m, 每本书有1<=x<=100000页, 把这些书分配给k个抄写员,要求分配给某个抄写员的那些书的编号必须是连续的。每个抄写员的速度是相同的,你的任务就是找到一个最佳的分配方案,使得所有书被抄完所用的时间最少。

输入:

输入可能包含多个测试样例。
第一行仅包含正整数 n,表示测试案例的个数。
对于每个测试案例,每个案例由两行组成,在第一行中,有两个整数m和 k, 1<=k<=m<=500。 在第二行中,有m个整数用空格分隔。 所有这些值都为正且小于100000。

输出:

对应每个测试案例,
输出一行数字,代表最佳的分配方案全部抄写完毕所需要的时间。

样例输入:
2
9 3
100 200 300 400 500 600 700 800 900
5 4
100 100 100 100 100
样例输出:
1700
200

题目的思路很明了,就是最大值最小化(我大一看过类似的题目,一直看不懂,还以为是dp一类的问题),先猜一个数字v(范围是最多的书的页码数max--总页码数sum。为什么不知0 -- sum? 我还没想到原因),然后从左到右统计书的页数,这里也用了一点贪心的思想,每个人都抄不超过v页的尽可能多的书,当超过了v后,就要给下一个人抄,直到全部书都抄完成。看需要的人数cnt, 再用cnt和输入的k比较,如果cnt < k,说明这种方法可取;如果cnt > k 说明 需要的人数超出了上限,不可取。(这就是judge()函数的内容),

如果judge(v) == 1,那么 每个人还可以抄更少的页数来达到最大值最小化的目的。这里用二分的方法提高效率。

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <vector>
#include <queue>
#include <stack>
#include <map>
#include <cmath>

using namespace std;
const int INF = 0x7fffffff;
int a[600];
int n, k;

bool judge(int mid)
{
    int cnt = 1;
    int sum = 0;
    for(int i = 0; i < n; ++i)
    {
        if(sum + a[i] <= mid)
            sum += a[i];
        else
        {
            sum = a[i];
            cnt++;
        }
    }

    return (cnt <= k);
}

int main()
{
   // freopen("1.txt", "r", stdin);
	int t;
	cin >> t;
	while(t--)
        {
            cin >> n >> k;
            int sum = 0;
            int maxx = 0;
        for(int i = 0; i < n; ++i)
        {
            cin >> a[i];
            sum += a[i];
            if(maxx < a[i])   //*********①*********
                maxx = a[i]; 
        }

        int lb = maxx, ub = sum;   //**********⑤**********
        int mid;
        while(lb < ub)             <span style="font-family: Arial, Helvetica, sans-serif;">//**********②***********</span>

        {
            mid = lb+(ub-lb)/2;    <span style="font-family: Arial, Helvetica, sans-serif;">//**********③***********</span>


            if(judge(mid))
                ub = mid;        <span style="font-family: Arial, Helvetica, sans-serif;">//**********</span><span style="font-family: Arial, Helvetica, sans-serif;">②</span><span style="font-family: Arial, Helvetica, sans-serif;">***********</span><span style="font-family: Arial, Helvetica, sans-serif;">
</span>
            else
                lb = mid+1;      <span style="font-family: Arial, Helvetica, sans-serif;">//**********②************</span>

        }
        cout << lb << endl;      //*********④*********
    }
    return 0;
}

共有7处需要注意

① 这是最不应该犯的错误,我把他写成了if(maxx > a[i])。


② while(lb < ub)  ,有的代码会写成while(lb <= ub) 或者 while(lb -1 < rb). 要注意三种不同的判断条件时,下面对于mid的更新也是不同的。比如 0  1  2,要找2,

第一次:mid = (2+0) / 2; mid == 1 < 2

假如 lb = mid的话。第二次查找时  lb = 1, ub = 2;  lb < rb, mid = (1+2)/ 2 = 1; 第三次查找时  lb = 1, ub = 2;  lb < rb, mid = (1+2)/ 2 = 1,会造成死循环。

由c和c++截尾取整的特性可知,当lb 刚好比ub小1时,如果lb = mid 的话,会永远处于同一种情况(lb + 1 = ub).

而为什么ub 可以直接取mid?因为取整是截尾取整,跟比他大的数无关。


③ mid = lb+(ub-lb)/2;  这样写是为了防止lb+ub造成的溢出。


④ cout << lb << endl;  其实写成  cout << ub << endl;也是可以的,  比如说 lb = 101   ub = 102  mid = 101, 那么下一步lb = 101  mid= 101 ub = 102,如果101ok那么 ub = 101.lb = 101.     如果101 不ok,那么  lb = 102  ub = 102.  就是说无论如何   最后的lb 和 ub都是相等的因为while(lb < ub)嘛,而且不可能出现lb > rb 的情况。

但是,千万不能写成 cout << mid << endl; 或者 cout << mid+1 << enl;

因为 当最后 lb mid  rb 分别为   1700 1700 1701 ,而答案为1700 时,输出mid+1 = 1701 明显是错的

而当lb mid rb 分别为 198 199 200 ,答案为200 时,199 < 200 造成cnt > k, judge(199) == false, 下一步该是lb = mid +1 = 200 而此时  mid 的值依然是199,因为循环是while(lb < ub),此时lb = ub== 200 ,但是mid却还是上次循环中的199.所以输出199 显然是错的。



⑤ 一开始二分查找的下界应该是maxx ,而不是0,为什么会这样我还没弄清楚。



先写到这,想到什么后面再补充。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值