第一节二分

第一节: 二分

定义:(一遍恒比这个值小一遍恒比这个值大为了找出这个值第一次或最后一次出现的位置所以用二分)

注意,这里的有序是广义的有序,如果一个数组中的左侧或者右侧都满足某一种条件,而另一侧都不满足这种条件,也可以看作是一种有序(如果把满足条件看做 ,不满足看做 ,至少对于这个条件的这一维度是有序的)。换言之,二分搜索法可以用来查找满足某种条件的最大(最小)的值。

整数二分模版
bool check(int x) {/* ... */} // 检查x是否满足某种性质

// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
int bsearch_1(int l, int r)//第一次出现
{
    while (l < r)
    {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;    // check()判断mid是否满足性质
        else l = mid + 1;
    }
    return l;
}
// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
int bsearch_2(int l, int r)//最后一次出现
{
    while (l < r)
    {
        int mid = l + r + 1 >> 1;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    return l;
}
浮点数二分模版
bool check(double x) {/* ... */} // 检查x是否满足某种性质

double bsearch_3(double l, double r)
{
    const double eps = 1e-6;   // eps 表示精度,取决于题目对精度的要求
    while (r - l > eps)
    {
        double mid = (l + r) / 2;
        if (check(mid)) r = mid;
        else l = mid;
    }
    return l;
}

二分总结

首先熟记模版,二分包括check函数和while(l<r)的循环   最后要l

接着了解二分用途:

用来确定满足某一条件的最值(即最先或最后出现)

然后完成程序:

写这个代码的步骤:

1.确定满足条件是什么-->即check函数

满足条件返回true

否则返回false

2.确定答案的左右边界

l=

r=

3.左右边界靠循环逼近找到答案

写while(l<r){

int mid=

if(check(mid)){

}

else{

}

 

}

紫色行的做优边界如何修改看check里面规定什么情况满足条件

然后由紫色行定黄色mid在整数二分里怎么变

两种可能

下取整

mid=l+r>>1;

上取整:

mid=l+r+1>>1

4.最后答案是l

 

二分例题:

例题一:分巧克力

儿童节那天有 K位小朋友到小明家做客。

小明拿出了珍藏的巧克力招待小朋友们。

小明一共有 N块巧克力,其中第 i 块是 Hi×Wi的方格组成的长方形。

为了公平起见,小明需要从这 N块巧克力中切出 K块巧克力分给小朋友们。

切出的巧克力需要满足:

  1. 形状是正方形,边长是整数
  2. 大小相同

例如一块 6×5 的巧克力可以切出 6 块 2×2的巧克力或者 2 块 3×3 的巧克力。

当然小朋友们都希望得到的巧克力尽可能大,你能帮小明计算出最大的边长是多少么?

输入格式

第一行包含两个整数 N 和 K

以下 N行每行包含两个整数 Hi 和 Wi

输入保证每位小朋友至少能获得一块 1×1 的巧克力。

输出格式

输出切出的正方形巧克力最大可能的边长。

数据范围

1N,K105
1Hi,Wi105

输入样例:

2 10
6 5
5 6

输出样例:

2

题目解析:

首先由每一位小朋友所得巧克力边长相同-->我们要求的就是满足条件的一个值(最大边长)-->对于每一块巧克力豆花粉乘若干块这个边长的小巧克力

所以关键就是求一个值

所以很容易想到二分算法

开始进行二分操作

1.check函数

我们要满足的是巧克力分出来的个数>=小朋友数

所以需要满足的条件是for循环所有巧克力划分出来的子块数求和>=小朋友数

每一块巧克力在确定划分边长是x时能划分出(h[i]/x)*(w[i]/x)个子块

2.写左右边界

最终答案介于1~10

l=1

r=1e5

3.写while循环

while(l<r){

int mid=l+r+1>>1;//由下面的l,r变化-->确定mid上取整

if(check(mid)){

//划分块多了那么需要增加边长

l=mid;

}

else{

r=mid-1;

}

}

4.输出答案l

cout<<l;

 

 

 

 

第一题完整代码
#include<bits/stdc++.h>
using namespace std;
int n,k;//n块巧克力,k个小朋友
const int N=1e5+6;
int h[N];//所有巧克力的长
int w[N];//所有巧克力的宽
typedef long long int ll;
bool check(int x){
    ll res=0;
    for(int i=0;i<n;i++){
        res+=(ll)(h[i]/x)*(w[i]/x);
    }
    if(res>=k)return true;
    return false;
}
int main(){
    cin>>n>>k;
    for(int i=0;i<n;i++)cin>>h[i]>>w[i];
    //进行二分(先定l,r然后不断通过check函数(满足什么条件)来找到想要的值)
   //巧克力边长至少为1
    int l=1;
    //巧克力边长最多为10的5次方
    int r=1e5;
    while(l<r){
        int mid=l+r+1>>1;
        if(check(mid)){
            l=mid;
        }
        else r=mid-1;
    }
    cout<<l;
    return 0;
}

第二题:炼金属

小蓝有一个神奇的炉子用于将普通金属 O冶炼成为一种特殊金属 X

这个炉子有一个称作转换率的属性 VV 是一个正整数,这意味着消耗 V 个普通金属 O恰好可以冶炼出一个特殊金属 X,当普通金属 O 的数目不足 V 时,无法继续冶炼。

现在给出了 N条冶炼记录,每条记录中包含两个整数 A和 B,这表示本次投入了 A 个普通金属 O,最终冶炼出了 B 个特殊金属 X

每条记录都是独立的,这意味着上一次没消耗完的普通金属 O不会累加到下一次的冶炼当中。

根据这 N 条冶炼记录,请你推测出转换率 V的最小值和最大值分别可能是多少,题目保证评测数据不存在无解的情况

输入格式

第一行一个整数 N,表示冶炼记录的数目。

接下来输入 N行,每行两个整数 AB含义如题目所述。

输出格式

输出两个整数,分别表示 V可能的最小值和最大值,中间用空格分开。

数据范围

对于 30%的评测用例,1N102
对于 60%的评测用例,1N103
对于 100%的评测用例,1N1041BA109

输入样例:

3
75 3
53 2
59 2

输出样例:

20 25

样例解释

当 V=20 时,有:75/20=353/20=259/20=2,可以看到符合所有冶炼记录。

当 V=25时,有:75/25=353/25=259/25=2,可以看到符合所有冶炼记录。

且再也找不到比 20 更小或者比 25更大的符合条件的 V 值了

方法一:直接找规律

#include<bits/stdc++.h>
using namespace std;
const int N=1e4+5;
int a[N];
int b[N];
int n;

int main(){
    
    cin>>n;
    for(int i=0;i<n;i++)cin>>a[i]>>b[i];
    int zx=a[0]/(b[0]+1)+1;
   
    int zd=a[0]/b[0];
    for(int i=1;i<n;i++){
        zx=max(zx,a[i]/(b[i]+1)+1);//最小
   
        zd=min(zd,a[i]/b[i]);//最大
    }
    cout<<zx<<" "<<zd;
    return 0;
}

方法二:二分

第二题完整代码
 #include<bits/stdc++.h>
using namespace std;
const int N=1e4+6;
int n;
int bi(int m,int n){
    int l=1;
    int r=1e9+1;
    while(l<r){
       int mid=l+r>>1;
       if(m/mid<=n){
           r=mid;
       }
       else{
           l=mid+1;
       }
    }
    return l;
}
int main(){
    cin>>n;
    int a,b;
    int zx=1;
    int zd=1e9;
    for(int i=0;i<n;i++){
        cin>>a>>b;
        zx=max(zx,bi(a,b));//最小
        zd=min(zd,bi(a,b-1)-1);//最大
    }
    cout<<zx<<" "<<zd;
    return 0;
}

第三题:数的范围

给定一个按照升序排列的长度为 n 的整数数组,以及 q 个查询。

对于每个查询,返回一个元素 k的起始位置和终止位置(位置从 0开始计数)。

如果数组中不存在该元素,则返回 -1 -1

输入格式

第一行包含整数 n 和 q,表示数组长度和询问个数。

第二行包含 n个整数(均在 110000 范围内),表示完整数组。

接下来 q 行,每行包含一个整数 k,表示一个询问元素。

输出格式

共 q行,每行包含两个整数,表示所求元素的起始位置和终止位置。

如果数组中不存在该元素,则返回 -1 -1

数据范围

1n100000
1q10000
1k10000

输入样例:

6 3
1 2 2 3 3 4
3
4
5

输出样例:

3 4
5 5
-1 -1
第三题完整代码
 #include<bits/stdc++.h>
using namespace std;
int n;
int q;
int k;
const int N=1e5+6;
int a[N];
int b1(int k){//找最后一次
int l=0;
int r=n-1;
while(l<r){
    int mid=l+r+1>>1;
    if(a[mid]<=k){
        l=mid;
    }
    else{
        r=mid-1;
    }
}
  if(a[l]==k)  return l;
  return -1;
}
int b2(int k){//找第一次出现<=
    int l=0;
    int r=n-1;
    while(l<r){
        int mid=l+r>>1;
        if(a[mid]>=k){
            r=mid;
        }
        else{
            l=mid+1;
        }
    }
    if(a[l]==k)return l;
    return -1;
}
int main(){
    cin>>n>>q;//n个数q次询问
    for(int i=0;i<n;i++)cin>>a[i];
    while(q--){
        cin>>k;
        cout<<b2(k)<<" "<<b1(k)<<endl;
    }
    return 0;
}

分析第三题:

注意:

1.二分时有边界不是1e5-1因为只有已经输入的数组是升序的,所以有效下标0~n-1

2.如何判断是否找到

找到的条件a[l]=k;

就可以返回l

否则就是没找到就返回-1

3.

最早出现,最晚出现对应的分别是哪一个整数二分模版

识别方法:取分界点

 

posted @   Annaprincess  阅读(7)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示