大一寒假培训(六)——二分查找

大一寒假培训(六)——二分查找

一个非常神奇的算法/思想:
二分,分的是答案,直接在答案在的区间范围中二分,分出一个值,就判断是不是答案,并进行转移
如果已知候选答案的范围(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;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值