初学算法----二分查找

二分查找不光是查找值;

1.假定一个值判断是否可行,

2.最大值最小化;

3.最小值最大化;

4.最大平均值;

其模型:

求满足f(x)的条件的最大(最小)的x:

1.首先判断f(x)是什么;

2.然后要求满足f(x)的条件的最大(最小)的x,对于任意满足f(x)的x`>=x(x`<=x),可用二分搜索,left=0,right=max;

3.不断二分直到满足条件;

还有一些需要转化为可以用二分查找的题目:

如在1000000个元素的数组中找两个数相加使和为M;

则可以先排序,再枚举元素,然后用NUM=M-array[i],再来找数组中是否有NUM;

----------------------------------------------------------------------------------------------------------

辨析出口条件     left<=right    还是   right-left>1

1.对于 left<=right;

这个是对于 解集的范围在[left,right] 中,设初始值时 left=0,right=size-1(或size) 一定要让left与right一开始就在解集的范围内,并在两端;

因为在[left,right]中,当left==right时还有一个元素要判断.

同时,要清楚二分法也是数学逻辑推导;

如:在数组(size的长度)寻找小于num的最大下标:

若这样设left=0,right=size-1;

mid=left+(right-left)/2;

当 array[mid]>=num,说明解的范围一定在[left,mid-1]中,则right=mid-1;

当 array[mid]<num,说明解的范围一定在[mid,right]中,但不能 left=mid;

所以在出口条件为left<=right的情况下,left=mid+1;但还有一个问题是mid可能也是答案,则要一个变量保存它 pos=mid;

这里有一个好博客:https://blog.csdn.net/baipanshi4037/article/details/102047904

对于二分查找我有个大问题:就是为什么这个是返回pos?以及网线管理那道题:

这样是错的:

#include<bits/stdc++.h>
using namespace std;
#define MAXNUM 10005
int weblen[MAXNUM];
bool funcjudge(int len,int N,int K)
{
    int cnt=0;
    for (int i=0;i<N;i++)
    {
        cnt+=weblen[i]/len;
    }
    if (cnt>=K) return true;
    else return false;
}
int main()
{
    int N,K;
    cin>>N>>K;
    for (int i=0;i<N;i++)
    {
        double temp;
        cin>>temp;
        weblen[i]=ceil(temp*100);//防止奇奇怪怪的精度问题;
    }
    int l=100,r=1e7,mid,temp;
    while (l<=r)
    {
        mid=(l+r)/2;
        if (funcjudge(mid,N,K))
        {
            temp=mid;
            l=mid+1;
        }
        else
        {
            r=mid-1;
        }
    }
    printf ("%.2lf",temp/100.0);
    return 0;
}

但改一改:

这样是对的:

#include<bits/stdc++.h>
using namespace std;
#define MAXNUM 10005
int weblen[MAXNUM];
bool funcjudge(int len,int N,int K)
{
    int cnt=0;
    for (int i=0;i<N;i++)
    {
        cnt+=weblen[i]/len;
    }
    if (cnt>=K) return true;
    else return false;
}
int main()
{
    int N,K;
    cin>>N>>K;
    for (int i=0;i<N;i++)
    {
        double temp;
        cin>>temp;
        weblen[i]=ceil(temp*100);//防止奇奇怪怪的精度问题;
    }
    int l=0,r=1e7+1,mid,temp;
    while (r-l>1)
    {
        mid=(l+r)/2;
        if (funcjudge(mid,N,K))
        {
            l=mid;
        }
        else
        {
            r=mid;
        }
    }
    printf ("%.2lf",l/100.0);
    return 0;
}

 

2. 对于 right-left>1;

它是 解集 在[left,right) 或(left,right] 中 ,当 right-left=1 或 left==right 时都无元素了;

则 其初始化 left=-1 (left=0),right=size-1(right=size),总之,让一个不在解集里;

还是那个例子如:在数组(size的长度)寻找小于num的最大下标:

left=0,right=size;

当 array[mid]>=num;

解一定在[left,mid)中;

当 array[mid]<num;

解一定在[mid,right)中;

出口条件 right-left<1;

核心: 不断二分 缩小解的范围 直到解集元素为空,无法二分;

 

//----------------------------------------------------------------------------------------------------------------------------------

高精度的二分:

出口条件: right-left>1e-6(或者更高的精度,但不能太高,会死循环)

用 left=mid与right=mid;

//--------------------------------------------------------------------------------------------------------------------------------

最大值最小化 与 最小值最大化 问题;

有两个例子:1.农夫与奶牛;

2.跳河

河中跳房子

 1000ms  65536K

描述:

每年奶牛们都要举办各种特殊版本的跳房子比赛,包括在河里从一个岩石跳到另一个岩石。这项激动人心的活动在一条长长的笔直河道中进行,在起点和离起点L远 (1 ≤ L≤ 1,000,000,000) 的终点处均有一个岩石。在起点和终点之间,有N (0 ≤ N ≤ 50,000) 个岩石,每个岩石与起点的距离分别为Di (0 < Di < L)。

在比赛过程中,奶牛轮流从起点出发,尝试到达终点,每一步只能从一个岩石跳到另一个岩石。当然,实力不济的奶牛是没有办法完成目标的。

农夫约翰为他的奶牛们感到自豪并且年年都观看了这项比赛。但随着时间的推移,看着其他农夫的胆小奶牛们在相距很近的岩石之间缓慢前行,他感到非常厌烦。他计划移走一些岩石,使得从起点到终点的过程中,最短的跳跃距离最长。他可以移走除起点和终点外的至多(0 ≤ M ≤ N) 个岩石。

请帮助约翰确定移走这些岩石后,最长可能的最短跳跃距离是多少?

 

输入:

第一行包含三个整数L, N, M,相邻两个整数之间用单个空格隔开。
接下来N行,每行一个整数,表示每个岩石与起点的距离。岩石按与起点距离从近到远给出,且不会有两个岩石出现在同一个位置。

输出:

一个整数,最长可能的最短跳跃距离。

样例输入:

25 5 2
2
11
14
17
21

样例输出:

4

这里用大佬的好方法:
1 #include<iostream>
 2 #define MAXN 50010
 3 using namespace std;
 4 int a[MAXN];
 5 int l, n, m;
 6 
 7 bool find(int mid)
 8 {
 9     int now = 0, num = 0;
10     for(int i = 1; i <= n + 1; ++ i)
11     {
12         if(a[i] - a[now] >= mid)
13         {
14             ++ num;   //num表示剩余石头数,当 a[i]-a[now]时,就一定要踩上那块石头了,既然踩上了,这块石头就一定存在,num++;这里他把河对岸的最后的石头也算上了,
15             now = i;
16         }        
17     }
18     if(num >= n - m + 1)//所以这里n-m后还+1;
19         return 1;
20     else
21         return 0;
22 } 
23 
24 int main()
25 {
26     cin >> l >> n >> m;
27     for(int i = 1; i <= n; ++i)
28         cin >> a[i];
29     int le = 0, mid, ans;
30     a[n+1] = l;
31     int ri = a[n+1];
32     while(le <= ri)
33     {
34         mid = (le + ri)/2;//mid为最长可能的最短跳跃距离 
35         if(find(mid))
36         {
37             ans = mid;
38             le = mid + 1;
39         }
40         else
41             ri = mid - 1;
42             
43     }
44     cout << ans << endl;
45     return 0;     
46 }
当然这道题还可用记录去掉的石头数,但我认为有问题;

//--------------------------------------------------------------------------------------------------------------------------------

月度开销

描述:

农夫约翰是一个精明的会计师。他意识到自己可能没有足够的钱来维持农场的运转了。他计算出并记录下了接下来 N (1 ≤ N ≤ 100,000) 天里每天需要的开销。

约翰打算为连续的M (1 ≤ M ≤ N) 个财政周期创建预算案,他把一个财政周期命名为fajo月。每个fajo月包含一天或连续的多天,每天被恰好包含在一个fajo月里。

约翰的目标是合理安排每个fajo月包含的天数,使得开销最多的fajo月的开销尽可能少。

 

输入:

第一行包含两个整数N,M,用单个空格隔开。
接下来N行,每行包含一个1到10000之间的整数,按顺序给出接下来N天里每天的开销。

输出:

一个整数,即最大月度开销的最小值。
因为若用枚举一定会超时,同时要求的最大月度开销的最小值一定在[一天中最大开销,n天总开销] 这个集合中,又这个集合表现为有序,设最大月度开销为自变量,不断求其最小值,可以用二分;
通过条件----m个财政周期,来不断缩小范围;
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
using namespace std;
int n,m,a[100009],maxx=0,tot,ans;
bool check(int);
int main()
{
scanf("%d%d",&n,&m);//输入n天,m个财政周期
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
tot+=a[i];//二分的右端点是总的开销
if(a[i]>maxx)maxx=a[i];//二分的左端点是n天中最大的开销
}
int l=maxx,r=tot,mid;
while(l<=r)//二分查找
{
mid=(l+r)>>1;
if(check(mid))//如果中间值可以
{
ans=mid;//记录答案
r=mid-1;//缩小范围
}
else
l=mid+1;//否则扩大范围

}
printf("%d",ans);//输出最小值
}
bool check(int x)//当最小值是x时是否可以
{
int sum=0,yfen=1;
for(int i=1;i<=n;i++)
{
if(sum+a[i]>x)//sum是一直累加,当超过x时月份++,说明这一天自己另一个月份
{
sum=a[i];
yfen++;
}
else
sum+=a[i];
}
if(yfen<=m)return 1;//月份等于m时正好分成m个月份,小于m时,可以将某些月份中的天数拆开组成新月份,满足分成m个月份
else
return 0;
}
对于为什么要从第一天就开始算,把第一天(或其他天)直接当一个月份不好吗?
posted @ 2021-12-03 00:03  次林梦叶  阅读(149)  评论(0)    收藏  举报