二分

解决范围

二分法可以用来解决这一系列具有单调性质的题,例如求单调函数的零点

其实在小学奥数中就用到了二分法

例如手动开根号,再比如猜数游戏

二分的具体过程就是先取一个中间值,判定一下正确答案在哪边,然后接着再二分,直到找到答案为止

二分法的本质是把求解问题转化成判定问题

优势

二分相对于暴力枚举来讲,判定次数会显著变少

具体来说,如果暴力枚举期望是O(N)次

那么二分只需要O(logN)次就可以得出答案

模板

  1. //整数版
  2. while(l<r)
  3. {
  4. mid=(l+r)/2;
  5. if(check(mid)) r=mid;
  6. else l=mid+1;
  7. }
  8. while(l<r)
  9. {
  10. mid=(l+r+1)/2;//注意+1
  11. if(check(mid)) l=mid;
  12. else r=mid-1;
  13. }
  14. //小数版
  15. while(r-l>eps)
  16. {
  17. mid=(l+r)/2;
  18. if(check(mid)>0) l=mid;
  19. else r=mid-eps;
  20. }
  21. //其中eps=1e-6或1e-8;依照题而定

例题

题目1

我的生日要到了!根据习俗,我需要将一些派分给大家。我有N个不同口味、不同大小的派。有F个朋友会来参加我的派对,每个人会拿到一块派(必须一个派的一块,不能由几个派的小块拼成;可以是一整个派)。

我的朋友们都特别小气,如果有人拿到更大的一块,就会开始抱怨。因此所有人拿到的派是同样大小的(但不需要是同样形状的),虽然这样有些派会被浪费,但总比搞砸整个派对好。当然,我也要给自己留一块,而这一块也要和其他人的同样大小。

请问我们每个人拿到的派最大是多少?每个派都是一个高为1,半径不等的圆柱体。

代码1
  1. #include<iostream>
  2. #include<cstdio>
  3. #include<algorithm>
  4. #include<cstring>
  5. #include<sstream>
  6. #include<queue>
  7. #include<vector>
  8. #define N 100010
  9. #define ll long long
  10. #define dd double
  11. using namespace std;
  12. int n,f;
  13. dd a[N];
  14. const dd pie=3.1415926535;
  15. bool check(dd mid)
  16. {
  17. ll sum=0;
  18. for(int i=1;i<=n;i++)
  19. {
  20. sum+=a[i]/mid;
  21. }
  22. if(sum>=f) return 1;
  23. else return 0;
  24. }
  25. int main()
  26. {
  27. cin>>n>>f;
  28. dd l=0,r=0;
  29. for(int i=1;i<=n;i++)
  30. {
  31. dd x;
  32. cin>>x;
  33. a[i]=x*x*pie*1;
  34. r=max(a[i],r);
  35. }
  36. dd eps=0.001;
  37. while(r-l>eps)
  38. {
  39. dd mid=(l+r)/2.0;
  40. if(check(mid)) l=mid;
  41. else r=mid-eps;
  42. }
  43. printf("%0.3lf",l);
  44. return 0;
  45. }
题目2

把一个包含n个正整数的序列划分为m个连续的子序列(每个正整数恰好属于一个序列)。设第i个序列的各数之和为S(i),你的任务是让所有S(i)的最大值尽量小。

例如序列1 2 3 2 5 4划分成3个序列的最优方案为1 2 3|2 5 |4,其中S(1)、S(2)、S(3)分别为6、7、4,最大值为7;如果划分成1 2|3 2|5 4,则最大值为9,不如刚才的好。

n<=106,所有数之和不超过109。

代码2
  1. #include<iostream>
  2. #include<cstdio>
  3. #include<algorithm>
  4. #include<cstring>
  5. #include<sstream>
  6. #include<queue>
  7. #include<vector>
  8. #define N 100010
  9. #define ll long long
  10. using namespace std;
  11. ll n,k,a[N];
  12. bool check(ll mid)
  13. {
  14. int sum=0,j=1;
  15. for(int i=1;i<=n;i++)
  16. {
  17. sum+=a[i];
  18. if(a[i]>mid) return 0;
  19. if(sum>mid)
  20. {
  21. j++;
  22. sum=a[i];
  23. }
  24. }
  25. if(j>k) return 0;
  26. else return 1;
  27. }
  28. int main()
  29. {
  30. scanf("%d%d",&n,&k);
  31. ll l,r=0;
  32. l=-100;
  33. for(int i=1;i<=n;i++)
  34. {
  35. scanf("%d",&a[i]);
  36. l=max(l,a[i]);
  37. r+=a[i];
  38. }
  39. while(l<r)
  40. {
  41. ll mid=l+r>>1;
  42. if(check(mid)) r=mid;
  43. else l=mid+1;
  44. }
  45. cout<<l<<endl;
  46. return 0;
  47. }
题目3

公园里有n个水塘,需要把这n个水塘中的水排干,水塘中的水在自然条件下1个单位的时间可以蒸发A升水。现在买了1台抽水机,使用抽水机可以让你用1个单位的时间使每个水塘除开自然蒸发的A升水外,还可抽B升水,但在1个单位的时间内只能对1个水塘使用。

要你求出排干所有水塘的最少时间(水塘中的水为0时为排干)。

代码3
  1. #include<iostream>
  2. #include<cstdio>
  3. #include<algorithm>
  4. #include<cstring>
  5. #include<sstream>
  6. #include<queue>
  7. #include<vector>
  8. #define N 100010
  9. #define ll long long
  10. using namespace std;
  11. ll n,a[N],A,B;
  12. bool check(ll mid)
  13. {
  14. int sum=0;
  15. int jian=mid*A;
  16. for(int i=1;i<=n;i++)
  17. {
  18. int ai=a[i]-jian;
  19. if(ai>0)
  20. {
  21. sum+=ai/B;
  22. if(ai%B!=0) sum++;
  23. }
  24. }
  25. if(sum>mid) return 0;
  26. else return 1;
  27. }
  28. int main()
  29. {
  30. cin>>n>>A>>B;
  31. ll l=0,r=0;
  32. for(int i=1;i<=n;i++)
  33. {
  34. cin>>a[i];
  35. r+=a[i];
  36. }
  37. while(l<r)
  38. {
  39. ll mid=l+r>>1;
  40. if(check(mid)) r=mid;
  41. else l=mid+1;
  42. }
  43. cout<<l<<endl;
  44. return 0;
  45. }
  46. /*
  47. 5 3 4
  48. 19 12 15 23 7
  49. 答案:5
  50. */
题目4

给出两个长度为n的正整数有序数组A和B, 在A和B中各任取一个, 可以得到n×n个积. 求第n小的元素。

n<=100000

思路4

判定有多少乘积小于这个答案就可以继续二分

但是怎么判定呢?

由于两个数组都是有序的,所以A数组中可行的乘积对应B数组一定是从头开始的一段序列,并且范围逐渐变小

这样我们O(N)扫一遍,用一个指针维护一下B数组合法位置就可以了

代码4
  1. #include<iostream>
  2. #include<cstdio>
  3. #include<algorithm>
  4. #include<cstring>
  5. #include<sstream>
  6. #include<queue>
  7. #include<vector>
  8. #define N 100010
  9. #define ll long long
  10. #define dd double
  11. using namespace std;
  12. ll n/*,m*/,a[N],b[N];
  13. bool check(ll mid)
  14. {
  15. int sum=0,j=1,i=n;
  16. while(i>=1&&j<=n)
  17. {
  18. if(a[i]*b[j]<mid)
  19. {
  20. sum+=i;
  21. j++;
  22. }
  23. else i--;
  24. }
  25. //cout<<sum+1<<endl;
  26. sum++;
  27. if(sum<=n) return 1;
  28. else return 0;
  29. }
  30. int main()
  31. {
  32. cin>>n;
  33. ll l=0,r=0;
  34. ll max1;
  35. for(int i=1;i<=n;i++) cin>>a[i],r=max(r,a[i]);
  36. for(int i=1;i<=n;i++) cin>>b[i],max1=max(max1,b[i]);
  37. r=max1*r;
  38. //cin>>m;check(m);
  39. while(l<r)
  40. {
  41. ll mid=l+r+1>>1;
  42. if(check(mid)) l=mid;
  43. else r=mid-1;
  44. }
  45. cout<<l<<endl;
  46. return 0;
  47. }
  48. /*
  49. #include<iostream>
  50. #include<cstdio>
  51. #include<algorithm>
  52. #include<cstring>
  53. #include<sstream>
  54. #include<queue>
  55. #include<vector>
  56. #define N 100010
  57. #define ll long long
  58. #define dd double
  59. using namespace std;
  60. ll n,a[N],b[N],c[N],tail;
  61. int main()
  62. {
  63. cin>>n;
  64. for(int i=1;i<=n;i++) cin>>a[i];
  65. for(int i=1;i<=n;i++) cin>>b[i];
  66. for(int i=1;i<=n;i++)
  67. for(int j=1;j<=n;j++)
  68. c[++tail]=a[i]*b[j];
  69. sort(c+1,c+tail+1);
  70. cout<<c[n];
  71. }
  72. */
  73. /*
  74. 5
  75. 12 23 112 231 345
  76. 23 123 423 2390 8492
  77. 答案:2829
  78. 5
  79. 1 2 3 4 5
  80. 2 3 4 5 6
  81. 答案:5
  82. */
题目5

一年一度的“跳石头”比赛又要开始了!

这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石。组委会已经选择好了两块岩石作为比赛起点和终点。在起点和终点之间,有 N 块岩石(不含起点和终点的岩石)。在比赛过程中,选手们将从起点出发,每一步跳向相邻的岩石,直至到达终点。

为了提高比赛难度,组委会计划移走一些岩石,使得选手们在比赛过程中的最短跳跃距离尽可能长。由于预算限制,组委会至多从起点和终点之间移走 M 块岩石(不能移走起点和终点的岩石)。

求最短跳跃距离的最大值

N,M<=50000

代码5
  1. #include<iostream>
  2. #include<cstdio>
  3. #include<algorithm>
  4. #include<cstring>
  5. #include<sstream>
  6. #include<queue>
  7. #include<map>
  8. #include<vector>
  9. #include<set>
  10. #include<deque>
  11. #define dd double
  12. #define ll long long
  13. #define N 10000100
  14. using namespace std;
  15. ll n,m,a[N];
  16. ll l;
  17. bool check(ll mid)
  18. {
  19. int now=0;
  20. int sum=0;
  21. for(int i=1;i<=n+1;i++)
  22. {
  23. if(a[i]-a[now]<mid) sum++;
  24. else now=i;
  25. }
  26. //cout<<mid<<" "<<sum<<endl;
  27. if(sum<=m) return 1;
  28. else return 0;
  29. }
  30. int main()
  31. {
  32. cin>>l>>n>>m;
  33. for(int i=1;i<=n;i++)
  34. {
  35. cin>>a[i];
  36. }
  37. a[n+1]=l;
  38. ll r=l;
  39. l=0;
  40. while(l<r)
  41. {
  42. ll mid=l+r+1>>1;
  43. if(check(mid)) l=mid;
  44. else r=mid-1;
  45. }
  46. cout<<l<<endl;
  47. return 0;
  48. }

三分

与二分法类似,三分法可以用来解决具有单峰性质的题

三分的具体过程就是先取两个中间值,分别位于1/3和2/3处,根据单峰性判定一下正确答案在前2/3还是后2/3,然后接着再三分,直到找到答案或答案的近似值为止

二分法每次把答案范围缩小一半,三分法每次把答案范围变为原来的2/3,他们的时间复杂度都是O(log(n))的

题目1

在一个2维平面上有两条传送带,每一条传送带可以看成是一条线段。两条传送带分别为线段AB和线段CD。小y在AB上的移动速度为P,在CD上的移动速度为Q,在平面上的移动速度R。现在小y想从A点走到D点,他想知道最少需要走多长时间?

思路1

小y走的路径一定是三条线段组成的折线

如果把离开AB线段的点设为x,到达CD的点设为y,总时间设为z,那么z是关于x,y的二元函数

可以证明这个函数形如一个山丘,也就是说可以先三分x再三分y求出z的最值

代码1

详见https://www.cnblogs.com/TianMeng-hyl/p/12309214.html

浅谈二分—— by hyl天梦的更多相关文章

  1. 浅谈二分查找 JavaScript

    算法介绍 二分查找,也称折半查找,是一种在有序数组中查找特定元素的搜索算法.查找过程经历一下步骤: (1)从有序数组的中间的元素开始搜索,如果该元素正好是目标元素,则停止搜索并返回该元素的索引值,否则 ...

  2. 浅谈算法和数据结构: 七 二叉查找树 八 平衡查找树之2-3树 九 平衡查找树之红黑树 十 平衡查找树之B树

    http://www.cnblogs.com/yangecnu/p/Introduce-Binary-Search-Tree.html 前文介绍了符号表的两种实现,无序链表和有序数组,无序链表在插入的 ...

  3. 浅谈java类集框架和数据结构(2)

    继续上一篇浅谈java类集框架和数据结构(1)的内容 上一篇博文简介了java类集框架几大常见集合框架,这一篇博文主要分析一些接口特性以及性能优化. 一:List接口 List是最常见的数据结构了,主 ...

  4. 浅谈C#常用集合类的实现以及基本操作复杂度

    List 集合类是顺序线性表,Add操作是O(1)或是O(n)的,由于List的容量是动态扩容的,在未扩容之前,其Add操作是O(1),而在需要扩容的时候,会拷贝已存在的那些元素同时添加新的元素,此时 ...

  5. 浅谈dedecms模板引擎工作原理及其自定义标签

    浅谈dedecms模板引擎工作原理: 理解织梦模板引擎有什么意思? 可以更好地自定义标签.更多在于了解织梦系统,理解模板引擎是理解织梦工作原理的第一步. 理解织梦会使我们写PHP代码是更顺手,同时能学 ...

  6. Qt3升至Qt4需要注意的几件事项浅谈

    Qt3升至Qt4需要注意的几件事项浅谈 公司以前的项目是用Qt3写的,随着时间的推移慢慢显示出Qt3有多方面的限制,因此先公司决定用Qt4来改写这个项目,并为软件添加新功能,在此背景先编写此文章. 先 ...

  7. 浅谈 Fragment 生命周期

    版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 源码:AndroidDemo/Fragment 文中如有纰漏,欢迎大家留言指出. Fragment 是在 Android 3.0 中 ...

  8. 浅谈 LayoutInflater

    浅谈 LayoutInflater 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 源码:AndroidDemo/View 文中如有纰漏,欢迎大家留言指出. 在 Android 的 ...

  9. 浅谈Java的throw与throws

    转载:http://blog.csdn.net/luoweifu/article/details/10721543 我进行了一些加工,不是本人原创但比原博主要更完善~ 浅谈Java异常 以前虽然知道一 ...

随机推荐

  1. #ICCV2019论文阅读#Fully_convolutional_Features

    一 知识背景 3D scan&cloud points(点云)patch-based features,fully convolutional network, deep metric lea ...

  2. 实现antd下拉框动态添加内容(与数据库交互)

    antd下拉控件的动态内容添加(与数据库交互) antd这个框架给开发带来了极大的方便,但同时,我认为还有一些不方便的地方:常用的逻辑在文档中没有体现.需要前端开发经验的人才能快速上手,而我刚刚接触这 ...

  3. SQL server 基本语句

    --查询数据库是否存在 if exists ( select * from sysdatabases where [name]='TestDB') print 'Yes, the DB exists' ...

  4. Eclipse自动添加注释模板

    Eclipse使用自动注释:在Eclipse工具的Window\preferences\JAVA\Code Style\Code templates\Comments下设置以下模版 文件(Files) ...

  5. 原生js里的offset、client、scroll三大家族

    offset家族 自己的,用于获取元素自身尺寸 offsetWidth 和 offsetHeight 获取元素自身的宽度和高度,包括内容+边框+内边距 offsetLeft 和 offsetTop 距 ...

  6. django框架中的静态文件引入

    首先在项目文件中新建文件夹static 之后在settings.py中配置路径 如下图所示: 下一步在你刚创建的static文件夹中添加app的文件夹名称,例如:teacher,如下图: 之后在tea ...

  7. 解决el-tree lazy懒加载时,连续勾选前两个子节点后第二次进入默认选中时,将父节点也勾选的问题

    在用到el-tree的懒加载和默认勾选功能时,若第一次勾选前几个连续节点,第二次进入默认勾选时,由于el-tree子节点尚未完全加载(只加载出来前几个),默认勾选已经开始(已加载出来的子节点被默认勾选 ...

  8. 初识runtime

    首先需要加上头文件#import<objc/runtime.h>和#Import<objc/message.h>   将A中的某个方法替换成B中某个方法,且没有任何的耦合 这里 ...

  9. hadoop 基础

    common 一组分布式文件系统和通用I/O的组件与接口(序列化.java RPC和持久化数据结构) Avro 一种支持高效.跨语言的RPC以及永久存储数据的序列化系统 MapReduce 分布式数据 ...

  10. mysql复习1

    SQL语句分为以下三种类型: DML: Data Manipulation Language 数据操纵语言,用于查询与修改数据记录,包括如下SQL语句:INSERT:添加数据到数据库中UPDATE:修 ...