二分查找学习指南

前置芝士

查找最后一个<=q的数的下标

// l,r均初始化为开区间
int a[100010],n;
int find(int q){
    int l=0,r=n+1;//左右指针为开区间
    while(l+1<r){
        int mid=l+r>>1;
        if(a[mid]<=q) l=mid;
        else r=mid;
    }
    return l;//l指针始终在可行解中跳转
}

查找第一个>=q的数的下标

int find(int q){
    int l=0,r=n+1;
    while(l+1<r){
        int mid=l+r>>1;
        if(a[mid]>=q) r=mid;
        else l=mid;
    }
     return r;
}

求一个浮点数(-10000<=y<=10000)的三次方根

【最大化查找】

double find(double y){
    double l=-100,r=100;
    while(r-l>1e-5){
        double mid=(l+r)/2;
        if(mid*mid*mid<=y) l=mid;
        else r=mid;
    }
    return l;
}
int main() {
    double y;
    scanf("%lf",&y);
    printf("%.3lf\n",find(y));
    return 0;
}

【最小化查找】

double find(double y){
    double l=-100,r=100;
    while(r-l>1e-5){
        double mid=(l+r)/2;
        if(mid*mid*mid>=y) r=mid;
        else l=mid;
    }
    return r;
}

整数三分

【c++】

int l = 1,r = 100;
while(l < r) {
    int lmid = l + (r - l) / 3; // l + 1/3区间大小
    int rmid = r - (r - l) / 3;  // r - 1/3区间大小
    lans = cal(lmid),rans = cal(rmid);
  
    // 求凹函数的极小值
    if(lans <= rans) r = rmid - 1;
    else l = lmid + 1;
  
    // 求凸函数的极大值
    if(lasn >= rans) l = lmid + 1;
    else r = rmid - 1;
}
// 求凹函数的极小值
cout << min(lans,rans) << endl;
// 求凸函数的极大值
cout << max(lans,rans) << endl;

求函数凸点(整数三分)

使用两个点将之分为三段

midl = left + (right - left)/3;
midr= right - (right - left)/3;
  • 如果midl比midr更加靠近最值点,我们就令 right = midr- 1; 【舍弃远离的那一段】
  • 如果midr比midl更加靠近罪之颠,令left = midl + 1; 【舍弃远离的那一段】
int trisection_search(int[] arr){
        if(arr==null){
            return -1;
        }
        int left=0,right=arr.length-1;
        while(left<=right){
            int midl=left+(right-left)/3;
            int midr=right-(right-left)/3;
            if(arr[midl]==arr[midr]){
                return midl;
            }else if(arr[midl]>arr[midr]){
                right=midr-1;
            }else{
                left=midl+1;
            }
        }
        return -1;
    }

浮点三分

const double EPS = 1e-9;
while(r - l >= EPS) {
    double lmid = l + (r - l) / 3;
    double rmid = r - (r - l) / 3;
    lans = cal(lmid),rans = cal(rmid);
  
    // 求凹函数的极小值
    if(lans <= rans) r = rmid;
    else l = lmid;
  
    // 求凸函数的极大值
    if(lans >= rans) l = lmid;
    else r = rmid;
}
// 输出 l 或 r 都可
cout << l << endl;

打家劫舍IV

[problm description]

沿街有一排连续的房屋。每间房屋内都藏有一定的现金。现在有一位小偷计划从这些房屋中窃取现金。

由于相邻的房屋装有相互连通的防盗系统,所以小偷 不会窃取相邻的房屋

小偷的 窃取能力 定义为他在窃取过程中能从单间房屋中窃取的 最大金额

给你一个整数数组 nums 表示每间房屋存放的现金金额。形式上,从左起第 i 间房屋中放有 nums[i] 美元。

另给你一个整数 k ,表示窃贼将会窃取的 最少 房屋数。小偷总能窃取至少 k 间房屋。

返回小偷的 最小 窃取能力。

示例 1:

输入:nums = [2,3,5,9], k = 2
输出:5
解释:
小偷窃取至少 2 间房屋,共有 3 种方式:
- 窃取下标 0 和 2 处的房屋,窃取能力为 max(nums[0], nums[2]) = 5 。
- 窃取下标 0 和 3 处的房屋,窃取能力为 max(nums[0], nums[3]) = 9 。
- 窃取下标 1 和 3 处的房屋,窃取能力为 max(nums[1], nums[3]) = 9 。
因此,返回 min(5, 9, 9) = 5 。

示例 2:

输入:nums = [2,7,9,3,1], k = 2
输出:2
解释:共有 7 种窃取方式。窃取能力最小的情况所对应的方式是窃取下标 0 和 4 处的房屋。返回 max(nums[0], nums[4]) = 2 。

提示:

n == stations.length

1 <= n <= 10^5

0 <= stations[i] <= 10^5

0 <= r <= n - 1

0 <= k <= 10^9

[solved]

看到「最大化最小值」或者「最小化最大值」就要想到二分答案,这是一个固定的套路。

为什么?一般来说,二分的值越大,越能/不能满足要求;二分的值越小,越不能/能满足要求,有单调性,可以二分。

设二分的最大金额为 mx,定义 f[i]表示在前 i 个房屋中窃取金额不超过mx 的房屋的最大个数。

分类讨论:

不选第 i个房屋:f[i]=f[i−1];
选第 i 个房屋,前提是金额不超过 mx:f[i]=f[i−2]+1。
这两取最大值,即

f[i]=max⁡(f[i−1],f[i−2]+1)
代码实现时,可以用两个变量滚动计算。


最小化两个数组中的最大值

[problem description]

给你两个数组 arr1arr2 ,它们一开始都是空的。你需要往它们中添加正整数,使它们满足以下条件:

  • arr1 包含 uniqueCnt1 个 互不相同 的正整数,每个整数都 不能 被 divisor1 整除
  • arr2 包含 uniqueCnt2 个 互不相同 的正整数,每个整数都 不能divisor2 整除
  • arr1arr2 中的元素 互不相同

给你 divisor1divisor2uniqueCnt1uniqueCnt2 ,请你返回两个数组中 最大元素最小值

示例 1:

输入:divisor1 = 2, divisor2 = 7, uniqueCnt1 = 1, uniqueCnt2 = 3
输出:4
解释:
我们可以把前 4 个自然数划分到 arr1 和 arr2 中。
arr1 = [1] 和 arr2 = [2,3,4] 。
可以看出两个数组都满足条件。
最大值是 4 ,所以返回 4 。

示例 2:

输入:divisor1 = 3, divisor2 = 5, uniqueCnt1 = 2, uniqueCnt2 = 1
输出:3
解释:
arr1 = [1,2] 和 arr2 = [3] 满足所有条件。
最大值是 3 ,所以返回 3 。

示例 3:

输入:divisor1 = 2, divisor2 = 4, uniqueCnt1 = 8, uniqueCnt2 = 2
输出:15
解释:
最终数组为 arr1 = [1,3,5,7,9,11,13,15] 和 arr2 = [2,6] 。
上述方案是满足所有条件的最优解。

提示:

2 <= divisor1, divisor2 <= 10^5

1 <= uniqueCnt1, uniqueCnt2 < 10^9

2<= uniqueCnt1 + uniqueCnt2 <= 10^9

[solved]

下文把 divisor1和 divisor2 简写成 d1 和 d2,记 LCM 为 d1 和 d2 的最小公倍数。

由于:

能被d2整除但不能被d1整除的数,能在arr1中且不能在arr2中。

能被d1整除但不能被d2整除的数,能在arr2中且不能在arr1中。

既不能被d1整除也不能被d2整除的数,可以在arr1和arr2中。

\[有\left\lfloor\dfrac{x}{d_2}\right\rfloor - \left\lfloor\dfrac{x}{\textit{LCM}}\right\rfloor 个数是 arr1独享的; \]

\[有 \left\lfloor\dfrac{x}{d_1}\right\rfloor - \left\lfloor\dfrac{x}{\textit{LCM}}\right\rfloor个数是arr2独享的; \]

\[有x-\left\lfloor\dfrac{x}{d_1}\right\rfloor - \left\lfloor\dfrac{x}{d_2}\right\rfloor + \left\lfloor\dfrac{x}{\textit{LCM}}\right\rfloor 个数(根据容斥原理)是 arr1和 arr2共享的。 \]

去掉独享的,剩余的数字只能在共享中选择,因此:

\[x−⌊ d 1 x ⌋−⌊ d 2 x ⌋+⌊ LCM x ⌋≥max(uniqueCnt 1 −⌊ d 2 x ⌋+⌊ LCM x ⌋,0)+max(uniqueCnt 2 −⌊ d 1 x ⌋+⌊ LCM x ⌋,0) \]

为二分判定条件。

代码实现时,二分上界可以取

\[(uniqueCnt1+uniqueCnt2)⋅2−1 \]

因为最坏情况下 d1=d2=2只能取奇数。

posted @ 2023-10-17 20:26  White_Sheep  阅读(2)  评论(0编辑  收藏  举报