大一寒假培训(六)——二分查找
一个非常神奇的算法/思想:
二分,分的是答案,直接在答案在的区间范围中二分,分出一个值,就判断是不是答案,并进行转移
如果已知候选答案的范围(min,max)(单调有序),有时候我们不必通过计算得到答案,只需在此范围内应用“二分”的过程,逐渐靠近答案(最后,得到答案)!
通过二分的方法,大幅度地跳过一片没必要的比较和选择
首先介绍最典型的二分——二分查找
二分法求零点,把区间折半来找零点,虽然找不到具体的零点值但是可以确定大体零点的一个范围,达到一定精度后可以看作答案。
所以我们现在就是把这种思想转化为一个问题
即问题如下:给定一个有序的数组,查找k是否在数组中
分析:首先,将表中间位置记录的关键字与k比较,如果两者相等,则查找成功;否则利用中间位置记录将表分成前、后两个子区间,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子区间,否则进一步查找后一子区间。
重复以上过程,直到找到满足条件的记录,使查找成功,或直到子区间不存在为止,此时查找不成功.容易知道算法的时间复杂度为O(logN)。
先来看一个例子:
那如果要查找的数据没有怎么办呢,让我们再看一个例子:
此外,c++本身包括二分函数
upper_bound()与lower_bound()使用方法
都是二分函数
upper_bound返回第一个大于的元素的下标;
lower_bound返回第一个大于等于元素的下标;
下面便是习题了:
nefu 956 二分查找
Description
有n(1<=n<=1000005)个整数,已经按照从小到大顺序排列好,现在另外给一个整数x,请找出序列中第1个大于x的数的下标!
Input
输入数据包含多个测试实例,每组数据由两行组成,第一行是n和x,第二行是已经有序的n个整数的数列。
Output
对于每个测试实例,请找出序列中第1个大于x的数的下标!。
Sample Input
3 3
1 2 4
Sample Output
2
//本题默认的是从num[0]开始输入
#include <bits/stdc++.h>
using namespace std;
int main()
{
int n,x;
while(cin>>n>>x){
int num[n];
for(int i=0;i<n;i++)
cin>>num[i];
int l=0,r=n-1,mid;
/*while(l<r){ //二分查找的核心代码
mid=(l+r)/2;
if(num[mid]<x)
l=mid+1;
else
r=mid;
}*/
mid=upper_bound(num,num+n,x)-num; //也可直接套用公式
cout << mid << endl;
}
return 0;
}
nefu 1646 小清新的二分查找之旅
Description
小清新又在疯狂懵逼了,遇到了一道题,并且发誓绝对不会告诉别人:在题号899的题目上脸懵逼了好久,于是他决定强化一下题目900,以抒发心中的抑郁之气。所以……
给出一组整数,整数个数为n,n不超过1,000,000,问这组整数中是否有k,总共询问q次。
Input
多组输入。
每组输入输入:
第一行2个整数:n q ,1 <=q , n <= 1,000,000 。
第二行 有 n 个整数,已升序排序。所有数 <= 1 e 9+7.
第三行 有 q 个整数,用于查询。
每行各数据之间有空格隔开。
Output
对每个查询数据分别输出一行
存在输出:
no
不存在输出:
YES
Sample Input
5 2
1 2 3 4 5
2 10
Sample Output
no
YES
#include <bits/stdc++.h>
using namespace std;
int main()
{
int n,q,x;
while(~scanf("%d%d",&n,&q)){ //怕用cin直接TLE
int num[n];
for(int i=0;i<n;i++) //输入数据
scanf("%d",&num[i]);
for(int i=0;i<q;i++){ //查询q次
scanf("%d",&x);
int l=0,r=n-1,mid,flag=0;
while(l<=r){ //进行二分
mid=(l+r)/2;
if(num[mid]<x)
l=mid+1;
else if(num[mid]>x)
r=mid-1;
else if(num[mid]==x){//找到数据,进行输出,并打断循环
printf("no\n");
flag=1;
break;
}
}
if(flag==0) //如果flag==0,则说明二分查找是没有找到数据
printf("YES\n");
}
}
return 0;
}
nefu 1645 小清新的函数坐标-二分
Description
一天,小清新用一些奇奇怪怪的工具绘制函数图像,玩得不亦乐乎,期间发现了一个函数:
F(x) = 0.0001 x5 + 0.003 x3 + 0.5 x - 3 ,聪明的他一眼see穿了它的单调性,现在,小清新想标注一些点,已经写出了它们的 y 坐标值 ,聪明的你能帮助 可爱善良的小清新把对应的 x 坐标值找出来么。谢谢啦!
保证: -20 < x < 20 。答案精确到小数点后第4位。数据多组输入形式。
Input
(多组输入)每行一个实数 y
Output
每行一个四位小数 x
Sample Input
-356.957952
350.957952
Sample Output
-19.9995
19.9995
#include <bits/stdc++.h>
using namespace std;
double f(double x) //写好f(x)函数
{return 0.0001*x*x*x*x*x+0.003*x*x*x+0.5*x-3;}
int main()
{
ios::sync_with_stdio(0); //取消cin,cout与stdio的同步
double y;
while(cin>>y){
double l=-20,r=20,mid;
while(r-l>=1e-6){ //实数型数据进行相等比较时,可采用两者之差非常小的方法
mid=(r+l)/2;
if(f(mid)>y)
r=mid; //实数不加1
else
l=mid; //实数不减1
}
printf("%.4f\n",mid); //输出结果
}
return 0;
}
nefu 1647 小清新的二倍问题加强版-二分-桶排
Description
小清新又要使坏了,他发现二倍问题并不能难住大家,于是他决定悄咪咪的改一波数据。
于是:
给定2到10,000个不同的正整数,你的任务是计算这些数里面有多少个数对满足:数对中一个数是另一个数的两倍。比如给定1 4 3 2 9 7 18 22,得到的答案是3,因为2是1的两倍,4是2个两倍,18是9的两倍。
Input
输入包括n组测试数据。第一行一个正整数 n 。
接下来n行表示n组数据,每组数据为一行,给出2到10,000个两两不同且小于100,000的正整数。每一行最后一个数是0,表示这一行的结束后,这个数不属于那2到10,000个给定的正整数。
Output
对每组输入数据,输出一行,给出有多少个数对满足其中一个数是另一个数的两倍。
Sample Input
3
1 4 3 2 9 7 18 22 0
2 4 8 10 0
7 5 11 13 1 3 0
Sample Output
3
2
0
#include <bits/stdc++.h>
using namespace std;
int num[100001],a[100001];
int main()
{
ios::sync_with_stdio(0);
int n,x,ans,j;
cin>>n;
while(n--){
memset(num,0,sizeof(num)); //初始化数组
ans=0;
for(j=0;;j++){ //进行桶排
cin>>a[j];
if(a[j]==0) break; //数据为0时,停止输入
num[a[j]]=1;
}
for(int i=0;i<j;i++)
if(num[2*a[i]]==1) //如果a[i]的二倍存在,则计数
ans++;
cout << ans << endl;
}
return 0;
}
nefu 1303 简单几何-二分
Description
一个长方体体积为v1,长为r,高为h,宽为1。一个圆柱体体积为v2,底面半径为r,高为h。r的取值范围为0<r<=100000,h的取值范围为1<=h<=100000的整数。给出h求使v2与v1的差值小于等于r^π的r的最小值
Input
输入第一行为一个整数t,表示接下来有t组数据。第二行到第t+1行,每行一个整数表示h
Output
输出r并保留4位小数
Sample Input
4
1
2
3
4
Sample Output
2.4073
4.7047
6.8441
8.8921
#include <bits/stdc++.h>
using namespace std;
const double pai=acos(-1.0); //常用的π的定义方法
int main()
{
ios::sync_with_stdio(0);
int t,h;
while(cin>>t){
while(t--){
cin>>h;
double cha;
double l=0,r=100000,mid;
while(l<r){
mid=(l+r)/2;
cha=fabs(mid*h-pai*mid*mid*h);
if(r-l<=1e-8) break; //如果条件成立,则等同于r=l
if(cha<=pow(mid,pai))
r=mid;
else
l=mid;
}
printf("%.4f\n",mid);
}
}
return 0;
}
nefu 1648 小清新切绳子-二分
Description
小清新又双叒叕被WA疯了,原来是被double类型的精度给坑了,但题目那么好,怎么能不安利一波呢=。=
于是他悄悄地修改了下数据,安利给那些可爱的小可爱们(没错( ̄▽ ̄)~*),就是屏幕前的你们。
有N条绳子,它们的长度分别为Li。如果从它们中切割出K条长度相同的绳子,这K条绳子每条最长能有多长?
Input
多组输入!
第一行两个整数N和K,接下来一行N个整数,描述了每条绳子的长度Li ,以空格隔开。
对于100%的数据 1<=Li<10,000,000 , 1<=n,k<=10000
Output
切割后每条绳子的最大长度(一个整数)
Sample Input
4 11
802 743 457 539
Sample Output
200
#include <bits/stdc++.h>
using namespace std;
int length[10010];
int n,k;
int check(int len) //如果符合条件,则为1
{
int num=0;
for(int i=0;i<n;i++) //计算每个绳子能分成几段
num+=length[i]/len;
if(num>=k) return 1; //如果至少能分成k段,则符合条件
else return 0;
}
int main()
{
ios::sync_with_stdio(0);
while(cin>>n>>k){
for(int i=0;i<n;i++)
cin>>length[i];
int l=0,r=10000000,mid,ans=0;
while(l<=r){
mid=(l+r)/2;
if(check(mid)) //符合条件时,将其记入答案
l=mid+1,ans=mid;
else
r=mid-1;
}
cout << ans << endl;
}
return 0;
}
nefu 1211 卖古董-DP-二分
Description
你的朋友小明有n个古董,每个古董的价值给出,然后小明要在接下来的m天内将所有古董依次卖出(注意:必须依次卖出,也就是从第一个开始卖卖到最后一个),小明希望
的是这m天内每天卖出的价值和的最大值最小,你来帮助他把?
Input
一共T组数据,每组一个n和m,代表n个古董以及m天(1<=m<=n),然后下面n行为古董的价值,古董价值为1到10000(1<=n<=100000)
Output
输出m天内卖出的古董价值和的最大值(当然是最优的时候),我们希望的是这个最大值越小越好
Sample Input
3
7 5
100
400
300
100
500
101
400
4 3
2
6
2
4
4 2
2
6
2
4
Sample Output
500
6
8
Hint
分成几段 {100,400}{300,100},{500},{101}{400};这里分成了5段; 每段都 <=500;
500是很多成功分段方案中,每段的最大值当中最小的一组;
#include <bits/stdc++.h>
using namespace std;
int n,m;
int price[100010];
int check(int a)
{
int ans=0,sum=0;
for(int i=0;i<n;i++){
sum+=price[i];
if(sum>a){ //每当古董的价值大于a时,让最后一个古董重新算,并计数
sum=0;
i--; //最后一个古董重新计算
ans++;
}
}
ans++; //最后肯定有古董,另外计数
if(ans>m) return 1; //如果计入天数大于m,则不符合条件
else return 0; //否则符合条件
}
int main()
{
ios::sync_with_stdio(0);
int t;
cin>>t;
while(t--){
cin>>n>>m;
int l=0,r=0,mid,ans;
for(int i=0;i<n;i++){
cin>>price[i];
l=max(l,price[i]); //算出古董的最大价值
r+=price[i]; //算出古董的总价值
}
ans=0;
while(l<=r){
mid=(l+r)/2;
if(check(mid)) //不符合条件
l=mid+1;
else //符合条件,记入答案
r=mid-1,ans=mid;
}
cout << ans << endl;
}
return 0;
}
nefu 1751 切绳子实数版-二分
Description
有N条绳子,它们的长度分别为Li。如果从它们中切割出K条长度相同的
绳子,这K条绳子每条最长能有多长?答案保留到小数点后2位。
Input
第一行两个整数N和K,接下来N行,描述了每条绳子的长度Li。
Output
切割后每条绳子的最大长度。
Sample Input
4 11
8.02
7.43
4.57
5.39
Sample Output
2.00
//与 nefu 1648 小清新切绳子-二分 相似
//开始时,把绳子长度乘100,最后把绳子的最大长度除以100,即为所求
#include <bits/stdc++.h>
using namespace std;
double length1[10010];
int length2[10010];
int n,k;
int check(int len)
{
int num=0;
for(int i=0;i<n;i++)
num+=length2[i]/len; //对len要特判,len!=0
if(num>=k) return 1;
else return 0;
}
int main()
{
ios::sync_with_stdio(0);
while(cin>>n>>k){
for(int i=0;i<n;i++){
cin>>length1[i];
length2[i]=length1[i]*100;
}
int l=0,r=10000000,mid,ans=0;
while(l<=r){
mid=(l+r)/2;
if(mid==0) break; //对0要特判,否则会出现RE,因为会出现除以0的情况
if(check(mid))
l=mid+1,ans=mid;
else
r=mid-1;
}
printf("%.2f\n",ans/100.);
}
return 0;
}
nefu 1733 数列分段-二分
Description
对于给定的一个长度为N的正整数数列A-i,现要将其分成M(M≤N)段,并要求每段连续,且每段和的最大值最小。
关于最大值最小:
例如一数列4 2 4 5 1要分成3段
将其如下分段:
[4 2][4 5][1]
第一段和为6,第2段和为9,第3段和为1,和最大值为9。
将其如下分段:
[4][2 4][5 1]
第一段和为4,第22段和为6,第33段和为6,和最大值为6。
并且无论如何分段,最大值不会小于6。
所以可以得到要将数列4 2 4 5 1要分成3段,每段和的最大值最小为6。
Input
第11行包含两个正整数N,M。
第22行包含N个空格隔开的非负整数A_i
含义如题目所述。
Output
一个正整数,即每段和最大值最小为多少。
Sample Input
5 3
4 2 4 5 1
Sample Output
6
Hint
N≤100000,M≤N,A i之和小于1e9
//与 nefu 1211 卖古董-DP-二分 相似
#include <bits/stdc++.h>
using namespace std;
int n,m;
int num[100010];
int check(int a)
{
int ans=1,sum=0;
for(int i=0;i<n;i++){
sum+=num[i];
if(sum>a){
sum=num[i];
ans++;
}
}
if(ans>m) return 1;
else return 0;
}
int main()
{
ios::sync_with_stdio(0);
cin>>n>>m;
int l=0,r=0,mid,ans;
for(int i=0;i<n;i++){
cin>>num[i];
l=max(l,num[i]);
r+=num[i];
}
ans=0;
while(l<=r){
mid=(l+r)/2;
if(check(mid))
l=mid+1;
else
r=mid-1,ans=mid;
}
cout << ans << endl;
return 0;
}
nefu 1245 二分查找加强版
Description
有n(1<=n<=2000005)个整数,是乱序的,现在另外给一个整数x,请找出序列排序后的第1个大于x的数的下标!
Input
输入数据包含多个测试实例,每组数据由两行组成,第一行是n和x,第二行是已经有序的n个整数的数列。
Output
对于每个测试实例,请找出从小到大排序后的序列中第1个大于x的数的下标!。
Sample Input
3 3
1 4 2
Sample Output
2
#include <bits/stdc++.h>
using namespace std;
int main()
{
int n,x;
while(cin>>n>>x){
int num[n];
for(int i=0;i<n;i++)
cin>>num[i];
sort(num,num+n);
int l=0,r=n-1,mid;
while(l<=r){ //可以直接使用upper_bound函数
mid=(l+r)/2;
if(num[mid]<x)
l=mid+1;
else
r=mid-1;
}
cout << mid+1 << endl;
}
return 0;
}