由九度1502引出的对二分查找的一点总结v1.0
2015-11-24 14:29
375 查看
做了九度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,为什么会这样我还没弄清楚。
先写到这,想到什么后面再补充。
相关文章推荐
- 注册IIS
- log4j配置
- 使用局部索引来提升 PostgreSQL 的性能
- Sublime Text(转移)
- jQuery插件实现无缝滚动特效
- 通达OA系统故障解决案例记录
- JdbcTemplate.CLASS
- 通达OA系统故障解决案例记录
- 【Quick 3.3】资源脚本加密及热更新(三)热更新模块
- openssh之 openssh.spec
- cocos2d-x开发学习笔记(一)
- Comparison method violates its general contract!
- 了解和配置 PAM
- JdbcOperations 综合
- pigeon物联网平台- developer portal web服务设计及实现
- 面试题(十二)
- Qt Creator中常用的快捷键总结
- 11/24 初学网页设计
- 微软宣布Win7/8.1考证日期从2016年1月31日延至2016年7月31日
- 快速对齐你的BarTender条码