二分法的基本思路是对一个有序序列(递增递减都可以)查找时,测试一个中间下标处的值,若值比期待值小,则在更大的一侧进行查找(反之亦然),查找时再次二分。这比顺序访问要少很多访问量,效率很高。

设:low,hight,mid均为整型。以在一个降序arr[5]={5,4,2,1,0}中查找k=4时的下标为例,取low=0,hight=4,则mid=low+(hight-low)/2=2(若无溢出可直接相加取半),此时arr[mid]=2小于k,这时需要向值更大的一侧(左侧)查找,所以low不变,hight=mid=2,再次拆半查找,mid=low+(hight-low)/2=2=1,此时arr[mid]=4等于k。找到结果。

这就是二分查找或者叫折半查找的基本思想。因为代码比较简单,所以无需用效率较低的迭代来实现:

int mid;
while(true){
mid=(low+hight)/2;
if(arr[mid]<k){
hight=mid;
}else if(arr[mid]>k){
low=mid;
}else{
return mid;
}
}

代码看起来就是这样的。现在来考虑这样一个问题,若上述查找中,k=3将会出现什么情况:当mid=1即arr[mid]=4时,大于k,而后出现震荡无法收敛。实在是太糟糕了,所以我们要加上一个收敛的条件,以及没有找到结果时的返回:

int mid;
while(low<hight){
mid=(low+hight)/2;
if(arr[mid]<k){
hight=mid;
}else if(arr[mid]>k){
low=mid;
}else{
return mid;
}
}
return -1;

好吧,这看起来比较完美了。现在,我们总结一下:

在二分查找中,若没有找到恰好的结果(很多时候我们不知道我们要的结果的确切值),while的退出条件即收敛条件,这个条件首先应满足low<hight,否则可能陷入无限循环。

现在看另外一个典型的例子:若函数f(x)在区间[a,b]内的单调,且f(a)<0,f(b)>0,求在该区间内的f(0)的值,精确到1E-10。此时,我们不知道要求的确切值,那么只能利用while的收敛条件:

double k=1E-10;
double mid;
//k/=100;
while(hight-low<k){
mid=hight-low;
if(f(mid)<0){
low=mid;
}else{
hight=mid;
}
}
return mid;

对于这个单调递增函数,当f(mid)比0小的时候,我们增加下限,向上查找。收敛时要求mid和实际解y之间差距不超过k,即当mid-k<=y<=mid+k时退出,最糟糕的时候low或hight正好为解,此时另一方跨越k距离正好达到精度,即hight-low=k时退出,循环条件为hight-low<k。好吧,到这里还不算完成,我们的代码还有一个小问题,如果要求保留小数点后3位,那么四舍五入还隐藏着一个问题,如:当我们的mid取到小数点后4位:mid=1.2345四舍五入保留小数点后3位为1.235,那么第四位的5是精确的吗?话句话说,mid到底是1.23449还是1.23450呢?好吧,我们还需要再向后计算一位以使得小数点后第四位为准确值。即,四舍五入保留小数点后3位时,应计算到小数点后5位:即k/=100。

下面可以说一下这个烦恼了我昨天一天,今天半上午的矩形分割了,这个问题现在长得是酱紫的:

总时间限制: 1000ms 内存限制: 65536kB
描述
平面上有一个大矩形,其左下角坐标(0,0),右上角坐标(R,R)。大矩形内部包含一些小矩形,小矩形都平行于坐标轴且互不重叠。所有矩形的顶点都是整点。要求画一根平行于y轴的直线x=k(k是整数) ,使得这些小矩形落在直线左边的面积必须大于等于落在右边的面积,且两边面积之差最小。并且,要使得大矩形在直线左边的的面积尽可能大。注意:若直线穿过一个小矩形,将会把它切成两个部分,分属左右两侧。 输入
第一行是整数R,表示大矩形的右上角坐标是(R,R) (1 <= R <= 1,000,000)。
接下来的一行是整数N,表示一共有N个小矩形(0 < N <= 10000)。
再接下来有N 行。每行有4个整数,L,T, W 和 H, 表示有一个小矩形的左上角坐标是(L,T),宽度是W,高度是H (0<=L,T <= R, 0 < W,H <= R). 小矩形不会有位于大矩形之外的部分。
输出
输出整数n,表示答案应该是直线 x=n。 如果必要的话,x=R也可以是答案。
样例输入
1000
2
1 1 2 1
5 1 2 1
样例输出
5

这个描述可能理解起来有点困难。翻译一下:

 一个左下角在(0,0)宽度为R的正方形范围内,有若干不重叠的小矩形(x,y,w,h均为整数,其中w,h>0),要求在该范围内画一条x=k的直线满足如下要求:
0、k为正整数。 1、在该直线左侧的小矩形的面积和大于等于在该直线右侧小矩形的面积和。若小矩形被分割,则左侧部分计入左面面积和,右侧部分计入右侧面积和。 2、满足0、1的前提下,尽量靠右侧分割。

这个问题要求用二分法进行解答。很容易想到二分所求的结果就是左右面积差,但是这存在两个问题:

1、k取整时,如何收敛。即正好分割为左右相等时,很多情况下要把一个单位距离分割为两份,亦即切分若干小矩形。

2、尽量靠右侧分割。即直线正好落在一段没有小矩形的位置,此时右面若干单位上也没有小矩形,k如何尽量大。

先来看第一个问题:这属于我们前面提到的精度问题的类似问题——low,hight逼近解到什么程度收敛。显而易见,要求k为整数时,low+1=hight即收敛,所以循环条件为low+1<hight,或者写成hight-low>1。这样,二分结束时的low就是最小k值。

然后里解决问题二:既然k落在可能解上,那么只需要计算当前k值时左右面积差s,若右移k面积依然是s,那么继续右移,直到右移一次之后,左右面积差大于s即找到最大k。

PS:low不可能是解,因为若直线分割小矩形,则左移导致左侧面积减小,不满足题意;即使在直线不分割小矩形,也不满足尽量靠右原则。

这就是我对这个题目的理解,不过我通过的代码在解决问题1时思路不同:

一定有一个位置x可以使得左右小矩形面积差为0,但x不能用整数表示。所以,我使用了double来表示。在纠正了若干错误,提交无数次之后,得了9分……咳咳…………。这就是那份代码:

#include<iostream>
#include<cmath>
#include<cstring>
using namespace std;
struct srect{
int left;
int right;
int height;
long long s;
};
double meval(srect rect[],int rc,double x){
double ls=,rs=;
int i;
for(i=;i<rc;i++){
if(rect[i].left>=x){
rs+=rect[i].s;
}else if(rect[i].right<=x){
ls+=rect[i].s;
}else{
ls+=(x-rect[i].left)*rect[i].height;
rs+=(-x+rect[i].right)*rect[i].height;
}
}
return ls-rs;
} double mbsearch(srect rect[],int rc,double low ,double hight){
double mid,d;
while(int(low)<int(hight)){
mid=(hight+low)/;
d=meval(rect,rc,mid);
if(d<){
low=mid;
}else if(d>){
hight=mid;
}else{
return mid;
}
}
return mid;
} int main(){
int i,width,rc,br,x;
double s;
cin>>br>>rc;
srect rect[br];
for(i=;i<rc;i++){
cin>>rect[i].left>>width>>width>>rect[i].height;
rect[i].right=rect[i].left+width;
rect[i].s=width*rect[i].height;
}
x=ceil(mbsearch(rect,rc,,br));
s=meval(rect,rc,x);
while(s=meval(rect,rc,x+) && x<br){
x++;
}
cout<<x;
}

乍一看是一点问题也没有,于是在煎熬一天半之后,又煎熬了几分钟。发现评价函数返回的double值竟然…………不想等。好吧,我知道你精度挺高,但达不到1就是1的程度,修改一下比较条件:

while(abs(s-meval(rect,rc,x+1))<=1e-15 && x<br){
x++;
}

用double的最大精度限制一下就10分了。当然,用double效率要比用int要低,虽然我用了一个int(low)没有取真正的精确值。而且这份代码还有很明显的需要优化的部分:low,hight的初始值应该是所有小矩形的min(left)和max(right)——搜索的范围越小迭代次数越少。

从一个NOI题目再学习二分查找。的更多相关文章

  1. c/c++再学习:查找算法了解

    1.顺序查找 说明:顺序查找适合于存储结构为顺序存储或链接存储的线性表. 基本思想:顺序查找也称为线形查找,属于无序查找算法.从数据结构线形表的一端开始,顺序扫描,依次将扫描到的结点关键字与给定值k相 ...

  2. leetcode 31. Next Permutation (下一个排列,模拟,二分查找)

    题目链接 31. Next Permutation 题意 给定一段排列,输出其升序相邻的下一段排列.比如[1,3,2]的下一段排列为[2,1,3]. 注意排列呈环形,即[3,2,1]的下一段排列为[1 ...

  3. 跳表--怎么让一个有序链表能够进行"二分"查找?

    对于一个有序数组,如果要查找其中的一个数,我们可以使用二分查找(Binary Search)算法,将它的时间复杂度降低为O(logn).那查找一个有序链表,有没有办法将其时间复杂度也降低为O(logn ...

  4. [PTA] 数据结构与算法题目集 6-10 二分查找

    Position BinarySearch(List L, ElementType X) { int beg = 1; int end = L->Last; while (beg <= e ...

  5. 分治算法(二分查找)、STL函数库的应用第五弹——二分函数

    分治算法:二分查找!昨天刚说不写算法了,但是突然想起来没写过分治算法的博客,所以强迫症的我…… STL函数库第五弹——二分函数lower_bound().upper_bound().binary_se ...

  6. LC T668笔记 & 有关二分查找、第K小数、BFPRT算法

    LC T668笔记 [涉及知识:二分查找.第K小数.BFPRT算法] [以下内容仅为本人在做题学习中的所感所想,本人水平有限目前尚处学习阶段,如有错误及不妥之处还请各位大佬指正,请谅解,谢谢!] !! ...

  7. 【算法训练营day1】LeetCode704. 二分查找 LeetCode27. 移除元素

    [算法训练营day1]LeetCode704. 二分查找 LeetCode27. 移除元素 LeetCode704. 二分查找 题目链接:704. 二分查找 初次尝试 看到题目标题是二分查找,所以尝试 ...

  8. 使用二分查找判断某个数在某个区间中--如何判断某个IP地址所属的地区

    一,问题描述 给定100万个区间对,假设这些区间对是互不重叠的,如何判断某个数属于哪个区间? 首先需要对区间的特性进行分析:区间是不是有序的?有序是指:后一个区间的起始位置要大于前一个区间的终点位置. ...

  9. javascript数据结构与算法---检索算法(二分查找法、计算重复次数)

    javascript数据结构与算法---检索算法(二分查找法.计算重复次数) /*只需要查找元素是否存在数组,可以先将数组排序,再使用二分查找法*/ function qSort(arr){ if ( ...

随机推荐

  1. mui问题

    2016.7.27 1.当你的html不在文件夹的时候 引路径就不要加../   2.当用svn提交代码的时候要先右键项目->版本管理->与资源库同步,查看你的修改的地方和原来部署上去的文 ...

  2. vux 中popup 组件 Mask 遮罩在最上层问题的解决

    1. 问题描述:popup弹出层在遮罩层下面的 2.原因:因为滚动元素和mask遮罩层在同一级,vux框架默认把遮罩层放在body标签下的 3.解决方法:更改一下源码,把mask遮罩层放在popup同 ...

  3. css中一些常用技巧

    // css中引入字体文件 @font-face { font-family: msyh; /*这里是说明调用来的字体名字*/ src: url('../font/wryh.ttf'); /*这里是字 ...

  4. JScrollBar

    接到了GUI相关的task,从来没看Java的我只好各种百度加看书了.这里介绍了 JScrollBar 的简单应用.    话不多说,直接上代码和效果图. import java.awt.*; imp ...

  5. [jquery]折叠指定条件的表格

    最近在做财务报表时候,一些表格要做特定折叠效果 这里通过2个自定义属性来对表格之间的属性作关联 date-head和date-num,输出表格时候,可以按照这2个自定义属性给某些带父子层级关系的内容指 ...

  6. Python爬虫Scrapy框架入门(0)

    想学习爬虫,又想了解python语言,有个python高手推荐我看看scrapy. scrapy是一个python爬虫框架,据说很灵活,网上介绍该框架的信息很多,此处不再赘述.专心记录我自己遇到的问题 ...

  7. js中时间戳转化成时间格式

    function formatDate(timestamp){ var test = new Date(parseInt(timestamp) * 1000); var $year = test.ge ...

  8. Nhibernate随手记(1)

    学习Nhibernate的萌芽 今早有群里有人问Nhibernate的问题,没学过,刚好来了兴趣,无意很快在园子里下载到了一本Nhibernate3.0的电子书,内容非常不错,很快地看了扫了一下,再记 ...

  9. javascript数组去重的两个方法

    方法一: 创建一个临时数组,判断目标数组中每个元素是否在临时数组中,如果不在就添加进临时数组,最后return临时数组 <script> var arr=[1,2,3,4,5,1,2,3, ...

  10. html学习第一天笔记——第七章节

    第7章 CSS样式基本知识<span style="color:blue"> </span>嵌入式css样式,写在当前的文件中[**********]嵌入式 ...