【二分】二分查找模板、二分答案套路学习记录

学习资料

  1. A05 二分查找算法 最好的板子
  2. A06 二分答案 最好的套路
  3. 算法讲解006【入门】二分搜索
  4. 算法讲解051【必备】二分答案法与相关题目
  5. 二分查找 红蓝染色法【基础算法精讲 04】
  6. 二分查找的三种写法 注:其中第二种左闭右开的写法本质上是本文下面提到的特殊的左闭右闭写法,这个特殊的左闭右闭写法来自《算法竞赛进阶指南》0x04二分。二分查找图片中的第二列就是这个特殊左闭右闭写法。之所以加“特殊”二字是为了区分市面上另一个“正宗”左闭右闭写法,也就是二分查找图片中第三列的写法。
    这个特殊左闭右闭写法的优点是:始终保证答案位于二分区间内,且二分结束条件l==r对应的值恰好在答案所处位置,可以很自然地处理无解的情况,形式优美。其他两种写法都需要对返回的下标进行额外的特判。

无特殊说明的情况下,本文“左闭右闭”均指特殊的左闭有闭写法(由左闭右开转化而来)。


二分查找模板

image

image

已知>=,用其表示<=
大于等于找的是第一个大于等于目标数x的元素下标。
而小于等于x的最后一个数就是第一个大于等于x+1的数左边的数,用找>=x+1得到的下标再1就是<=x
image

已知<=,用其表示>=
小于等于找的是最后一个小于等于目标数x的元素下标。
而大于等于x的第一个元素就是最后一个小于等于x1的数右边的数,用找>=x1得到的下标再+1就是>=x
image

注1:整数二分中,在需要多次查找>=><=<的情况时,四种任写一种即可(一般写>=),剩下的三种都可以由其转化。
例如:在需要查找一个数第一次出现的位置和最后一次出现位置时,其实只需要写一个二分函数即可,剩下的都可以互相转化,如上图。
注2:如果要使用转化写法,特判不能写在二分函数内部,特判必须写在外面,函数内部只能根据情况返回 l 或者 r


34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)

开区间写法,左开右开,答案在区间(l,r)内,l是答案取值范围的1r是答案取值范围+1
关于返回值的判断:if(check(mid)==true)时更新谁就返回谁,因为答案在可行区上,哪个指针在可行区上移动,那该指针最后指向的就是答案(若有解)
举例:
if (check(mid)==true) r=mid 那函数最后就返回r
if (check(mid)==true) l=mid 那函数最后就返回l

对于开区间>=写法find1(int x),若目标数不存在于数组中,有三种情况:
(1)若目标数字比数组中所有数都小,返回下界1
(2)若目标数字比数组中所有数都大,返回上界+1
(3) 若(1)(2)均不成立,则返回第一个大于等于目标数的元素下标

如下图
image

对于<=写法find2(int x),若目标数不存在于数组中,也有三种情况
(1)若目标数字比数组中所有数都小,返回下界1
(2)若目标数字比数组中所有数都大,返回数组中最后一个元素的下标(即小于等于目标元素的最后一个元素的下标)
(3)若(1)(2)均不成立,则返回最后一个小于等于目标数的元素下标
如图:
image

>=<=均写,外部处理边界

class Solution {
public:
    int n;
    vector<int> res;
    int find1(vector<int>& a, int x)
    {
        int l = -1, r = n;
        while (l + 1 < r)
        {
            int mid = l + ((r - l) >> 1);
            if (a[mid] >= x) r = mid;
            else l = mid;
        }
        //开区间写法在无解时,指针可能指向上界+1或下界-1
        //此时直接拿这个下标访问数组会越界,在外面需要特判一下
        return r;
    }
    int find2(vector<int>& a, int x)
    {
        int l = -1, r = n;
        while (l + 1 < r)
        {
            int mid = l + ((r - l) >> 1);
            if (a[mid] <= x) l = mid;
            else r = mid;
        }
        //开区间写法在无解时,指针可能指向上界+1或下界-1
        //此时直接拿这个下标访问数组会越界,在外面需要特判一下
        return l;
    }
    vector<int> searchRange(vector<int>& nums, int target) {
        n = nums.size();
        if (n == 0) return {-1, -1};
        int x = find1(nums, target);
        //开区间特判下标==n或==-1的情况,if前半段解决=n,后半段解决-1
        //特判写法一
        //不需要特判x==-1,因为循环结束条件是l+1==r,若指向区间左端点必有l=-1+1=0,这不会越界
        //所以只需要判断x==n以及nums[x]与target是否相等即可
        if (x == n || nums[x] != target) return {-1, -1};
        else res.push_back(x);
        //特判写法二 
        // if (x < n && nums[x] == target) res.push_back(x);
        // else return {-1, -1};
        x = find2(nums, target);
        res.push_back(x);//这里不用判断nums[x]==target,因为不等于在32行就返回结果了
        return res;
    }
};

>=<=均写,函数内处理边界

开区间也可以在函数内判断,不存在就直接返回-1,这样就省去了外部判断。写法如下

class Solution {
public:
    int n;
    vector<int> res;
    int find1(vector<int>& a, int x) // 找x第一次出现的位置
    {
        int l = -1, r = n;
        while (l + 1 < r)
        {
            int mid = l + ((r - l) >> 1);
            if (a[mid] >= x) r = mid;
            else l = mid;
        }
        return (r < n && a[r] == x) ? r : -1;//返回时判断
    }
    int find2(vector<int>& a, int x) // 找x最后一次出现的位置
    {
        int l = -1, r = n;
        while (l + 1 < r)
        {
            int mid = l + ((r - l) >> 1);
            if (a[mid] <= x) l = mid;
            else r = mid;
        }
        return a[l] == x ? l : -1;//l+1=r结束,l不会越界,对比r可能取到n,会越界
    }
    vector<int> searchRange(vector<int>& nums, int target) {
        n = nums.size();
        if (n == 0) return {-1, -1};
        int x = find1(nums, target);
        if (x == -1) return {-1, -1};
        else res.push_back(x);
        x = find2(nums, target);
        res.push_back(x);
        return res;
    }
};

只写>=x,则<=x可转化为>=(x+1)1

转化写法,只写一个>=x的情况,<=x可转化为>=(x+1)1

class Solution {
public:
    int n;
    vector<int> res;
    int find1(vector<int>& a, int x)
    {
        int l = -1, r = n;
        while (l + 1 < r)
        {
            int mid = l + ((r - l) >> 1);
            if (a[mid] >= x) r = mid;
            else l = mid;
        }
        //开区间写法在无解时,指针可能指向上界+1或下界-1
        //此时直接拿这个下标访问数组会越界,在外面需要特判一下
        return r;
    }
    vector<int> searchRange(vector<int>& nums, int target) {
        n = nums.size();
        if (n == 0) return {-1, -1};
        int x = find1(nums, target);
        //开区间特判下标==n或==-1的情况,if前半段解决=n,后半段解决-1
        //特判写法一
        //不需要特判x==-1,因为循环结束条件是l+1==r,若指向区间左端点必有l=-1+1=0,这不会越界
        //所以只需要判断nums[x]与target是否相等即可
        if (x == n || nums[x] != target) return {-1, -1};
        else res.push_back(x);
        //特判写法二 
        // if (x < n && nums[x] == target) res.push_back(x);
        // else return {-1, -1};
        x = find1(nums, target + 1) - 1;// <=x转化为>=(x+1)-1
        res.push_back(x);
        return res;
    }
};

只写<=x,则>=x可转化为<=(x1)+1

转化写法,只写一个<=x,则>=x等价于<=(x1)+1

附上边界情况的样例:
1.查找的数大于所有数
image

2.查找的数小于所有数
image

class Solution {
public:
    int n;
    vector<int> res;
    int find2(vector<int>& a, int x)
    {
        int l = -1, r = n;
        while (l + 1 < r)
        {
            int mid = l + ((r - l) >> 1);
            if (a[mid] <= x) l = mid;
            else r = mid;
        }
        //开区间写法在无解时,指针可能指向上界+1或下界-1
        //此时直接拿这个下标访问数组会越界,在外面需要特判一下
        return l;
    }
    vector<int> searchRange(vector<int>& nums, int target) {
        n = nums.size();
        if (n == 0) return {-1, -1};
        int x = find2(nums, target - 1) + 1;//>=x等价于<=(x-1)+1
        if (x < n && nums[x] == target) res.push_back(x);
        else return {-1, -1};
        x = find2(nums, target);
        res.push_back(x);
        return res;
    }
};

左闭右闭写法

acwing板子写法,左闭右闭,答案始终在区间[l,r]内,l==r时退出循环,此时lr指向的位置就是答案,返回lr都可以

class Solution {
public:
    int n;
    vector<int> res;
    int find1(vector<int>& a, int x)
    {
        int l = 0, r = n - 1;
        while (l < r)
        {
            int mid = l + r >> 1;
            if (a[mid] >= x) r = mid;
            else l = mid + 1;
        }
        return r;//l也可
    }
    int find2(vector<int>& a, int x)
    {
        int l = 0, r = n - 1;
        while (l < r)
        {
            int mid = l + r + 1 >> 1;
            if (a[mid] <= x) l = mid;
            else r = mid - 1;
        }
        return r;//l也可
    }
    vector<int> searchRange(vector<int>& nums, int target) {
        n = nums.size();
        if (n == 0) return {-1, -1};
        int x = find1(nums, target);
        if (nums[x] == target) res.push_back(x);
        else return {-1, -1};
        x = find2(nums, target);
        res.push_back(x);//这里不用判断nums[x]==target,因为不等于在32行就返回结果了
        return res;
    }
};

P2249 【深基13.例1】查找

开区间写法,数组元素下标从0开始

#include <iostream>

using namespace std;

const int N = 1e6 + 10;

int n, m;
int a[N];

int find(int x)
{
    int l = -1, r = n;
    while (l + 1 < r)
    {
        int mid = l + r >> 1;
        if (a[mid] >= x) r = mid;
        else l = mid;
    }
    return (r < n && a[r] == x) ? r + 1 : -1;
}

int main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    cin >> n >> m;
    for (int i = 0; i < n; i++) cin >> a[i];
    while (m--)
    {
        int x;
        cin >> x;
        cout << find(x) << ' ';
    }
    return 0;
}

开区间写法,数组元素下标从1开始

#include <iostream>

using namespace std;

const int N = 1e6 + 10;

int n, m;
int a[N];

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

int main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    cin >> n >> m;
    for (int i = 1; i <= n; i++) cin >> a[i];
    while (m--)
    {
        int x;
        cin >> x;
        cout << find(x) << ' ';
    }
    return 0;
}

左闭右闭写法,数组元素下标从0开始

#include <iostream>

using namespace std;

const int N = 1e6 + 10;

int n, m;
int a[N];

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

int main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    cin >> n >> m;
    for (int i = 0; i < n; i++) cin >> a[i];
    while (m--)
    {
        int x;
        cin >> x;
        cout << find(x) << ' ';
    }
    return 0;
}

左闭右闭写法,数组元素下标从1开始

#include <iostream>

using namespace std;

const int N = 1e6 + 10;

int n, m;
int a[N];

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

int main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    cin >> n >> m;
    for (int i = 1; i <= n; i++) cin >> a[i];
    while (m--)
    {
        int x;
        cin >> x;
        cout << find(x) << ' ';
    }
    return 0;
}

789. 数的范围

开区间写法,数组元素下标从0开始

#include <iostream>

using namespace std;

const int N = 1e5 + 10;

int n, m;
int a[N];

int find1(int x)
{
    int l = -1, r = n;
    while (l + 1 < r)
    {
        int mid = l + r >> 1;
        if (a[mid] >= x) r = mid;
        else l = mid;
    }
    return (r < n && a[r] == x) ? r : -1;
}

int find2(int x)
{
    int l = -1, r = n;
    while (l + 1 < r)
    {
        int mid = l + r >> 1;
        if (a[mid] <= x) l = mid;
        else r = mid;
    }
    return a[l] == x ? l : -1;
}

int main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    cin >> n >> m;
    for (int i = 0; i < n; i++) cin >> a[i];
    while (m--)
    {
        int k;
        cin >> k;
        cout << find1(k) << ' ' << find2(k) << '\n';
    }
    return 0;
}

开区间写法,数组元素下标从1开始

#include <iostream>

using namespace std;

const int N = 1e5 + 10;

int n, m;
int a[N];

int find1(int x)
{
    int l = 0, r = n + 1;
    while (l + 1 < r)
    {
        int mid = l + r >> 1;
        if (a[mid] >= x) r = mid;
        else l = mid;
    }
    return (r < n + 1 && a[r] == x) ? r - 1 : -1;
}

int find2(int x)
{
    int l = 0, r = n + 1;
    while (l + 1 < r)
    {
        int mid = l + r >> 1;
        if (a[mid] <= x) l = mid;
        else r = mid;
    }
    return a[l] == x ? l - 1 : -1;
}

int main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    cin >> n >> m;
    for (int i = 1; i <= n; i++) cin >> a[i];
    while (m--)
    {
        int k;
        cin >> k;
        cout << find1(k) << ' ' << find2(k) << '\n';
    }
    return 0;
}

浮点数二分

P1024 [NOIP 2001 提高组] 一元三次方程求解

printf("%.2lf", )精确到小数点后两位
也可以写成

        if (!l) cout << fixed << setprecision(2) << i * 1.0 << ' ';
        else if (f(i) * f(i + 1) < 0)
            cout << fixed << setprecision(2) << find(i, i + 1) << ' ';
// Problem: P1024 [NOIP 2001 提高组] 一元三次方程求解
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P1024
// Memory Limit: 125 MB
// Time Limit: 1000 ms

#include <cmath>
#include <iomanip>
#include <iostream>

using namespace std;

double a, b, c, d;

double f(double x)
{
    return a * x * x * x + b * x * x + c * x + d;
}

double find(double l, double r)
{
    while (r - l > 1e-4)
    {
        double mid = (l + r) / 2;
        if (f(mid) * f(r) < 0) l = mid;
        else r = mid;
    }
    return l;
}

int main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    cin >> a >> b >> c >> d;
    for (int i = -100; i < 100; i++)
    {
        double l = f(i), r = f(i + 1);
        if (!l) printf("%.2lf ", i * 1.0);
        else if (f(i) * f(i + 1) < 0) printf("%.2lf ", find(i, i + 1));
    }
    return 0;
}

P1163 银行贷款

// Problem: P1163 银行贷款
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P1163
// Memory Limit: 125 MB
// Time Limit: 1000 ms

#include <iostream>

using namespace std;

int w0, w, m;

bool check(double mid)
{
    double s = w0;
    for (int i = 1; i <= m; i++) s = s * (1 + mid) - w;
    return s >= 0; // 也可以写>0
}

void solve()
{
    cin >> w0 >> w >> m;
    double l = 0, r = 10;
    while (r - l > 1e-5)
    {
        double mid = (l + r) / 2;
        if (check(mid)) r = mid;
        else l = mid;
    }
    printf("%.1lf", r * 100);
}

int main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int T = 1;
    while (T--) solve();
    return 0;
}

二分答案套路

image

口诀:
最大化左边,最小化右边
左增右减小等c,右增左减大等c
解释:
求最大化答案,可行区在左边,
求最小化答案,可行区在右边,
左增右减,左增:可行区在左侧且函数单调增,右减:可行区在右侧且函数单调减,则y<=c
右增左减,右增:可行区在右侧且函数单调增,左减:可行区在左侧且函数单调减,则y>=c

函数单调增加,求最大化答案

image

P2678 [NOIP 2015 提高组] 跳石头

// Problem: P2678 [NOIP 2015 提高组] 跳石头
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P2678
// Memory Limit: 128 MB
// Time Limit: 1000 ms

#include <algorithm>
#include <iostream>

using namespace std;
using i64 = long long;

const int N = 5e4 + 10;

int a, n, c;
int d[N];

bool check(int mid) // mid表示最短跳跃距离
{
    int pre = 0, sum = 0;// sum表示搬石头的次数
    for (int i = 1; i <= n + 1; i++)
    {
        // 如果当前距离比最短跳跃距离都小,那必须得搬石头
        if (d[i] - d[pre] < mid) sum++;
        else pre = i;// 当前距离>=最短跳跃距离,继续跳
    }
    return sum <= c; // 搬石头次数<=给定次数
}

void solve()
{
    cin >> a >> n >> c;
    for (int i = 1; i <= n; i++) cin >> d[i];
    d[n + 1] = a;
    int l = 0, r = a + 1; // 上界就是直接从起点跳到终点
    while (l + 1 < r)
    {
        int mid = l + (r - l >> 1);
        if (check(mid)) l = mid; // 搬石头次数没用够上限那就继续加大最短跳跃距离
        else r = mid;
    }
    cout << l;
}

int main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int T = 1;
    while (T--) solve();
    return 0;
}

P3743 小鸟的设备

二分答案+浮点二分

image

// Problem: P3743 小鸟的设备
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P3743
// Memory Limit: 125 MB
// Time Limit: 1000 ms

#include <iomanip>
#include <iostream>

using namespace std;

const int N = 1e5 + 10;

int n, p;
int a[N], b[N];

bool check(double mid)
{
    double sum = 0;
    for (int i = 0; i < n; i++)
    {
        if (a[i] * mid > b[i]) sum += (a[i] * mid - b[i]);
    }
    return sum <= mid * p;
}

void solve()
{
    cin >> n >> p;
    double s = 0;
    for (int i = 0; i < n; i++) cin >> a[i] >> b[i], s += a[i];
    if (s <= p) cout << fixed << setprecision(6) << -1.0;
    else
    {
        double l = 0, r = 1e10 + 1;
        while (r - l > 1e-6)
        {
            double mid = (l + r) / 2;
            if (check(mid)) l = mid;
            else r = mid;
        }
        cout << fixed << setprecision(6) << l;
    }
}

int main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int T = 1;
    while (T--) solve();
    return 0;
}

函数单调减少,求最大化答案

image

P1824 进击的奶牛

// Problem: P1824 进击的奶牛
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P1824
// Memory Limit: 125 MB
// Time Limit: 1000 ms

#include <algorithm>
#include <iostream>

using namespace std;

const int N = 1e5 + 10;

int n, c;
int a[N];

bool check(int mid)
{
    int sum = 0, last = -1e9;
    for (int i = 0; i < n; i++)
    {
        if (a[i] - last >= mid)
        {
            sum++;
            last = a[i];
        }
    }
    return sum >= c;
}

void solve()
{
    cin >> n >> c;
    for (int i = 0; i < n; i++) cin >> a[i];
    sort(a, a + n);
    int l = 0, r = *max_element(a, a + n) + 1;
    while (l + 1 < r)
    {
        int mid = l + (r - l >> 1);
        if (check(mid)) l = mid;
        else r = mid;
    }
    cout << l;
}

int main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int T = 1;
    while (T--) solve();
    return 0;
}

P1873 [COCI 2011/2012 #5] EKO / 砍树

// Problem: P1873 [COCI 2011/2012 #5] EKO / 砍树
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P1873
// Memory Limit: 125 MB
// Time Limit: 1000 ms

#include <algorithm>
#include <iostream>

using namespace std;
using i64 = long long;

const int N = 1e6 + 10;

int n, c;
int a[N];

bool check(int h)
{
    i64 sum = 0;
    for (int i = 0; i < n; i++)
    {
        if (a[i] > h) sum += (a[i] - h);
    }
    return sum >= c;
}

void solve()
{
    cin >> n >> c;
    for (int i = 0; i < n; i++) cin >> a[i];
    int l = 0, r = *max_element(a, a + n);
    while (l + 1 < r)
    {
        int mid = l + (r - l >> 1);
        if (check(mid)) l = mid;
        else r = mid;
    }
    cout << l;
}

int main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int T = 1;
    while (T--) solve();
    return 0;
}

P2440 木材加工

// Problem: P2440 木材加工
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P2440
// Memory Limit: 128 MB
// Time Limit: 1000 ms

#include <algorithm>
#include <iostream>

using namespace std;
using i64 = long long;

const int N = 1e5 + 10;

int n, c;
int a[N];

bool check(int mid) // 传入长度,计算该长度能分多少段,>=给定段数就继续分
{
    i64 sum = 0;
    for (int i = 0; i < n; i++) sum += a[i] / mid;
    return sum >= c;
}

void solve()
{
    cin >> n >> c;
    i64 s = 0;
    for (int i = 0; i < n; i++) cin >> a[i], s += a[i];
    int l = 0, r = s / c + 1; // 最大段数(上界)+1;
    while (l + 1 < r)
    {
        int mid = l + (r - l >> 1);
        if (check(mid)) l = mid;
        else r = mid;
    }
    cout << l;
}

int main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int T = 1;
    while (T--) solve();
    return 0;
}

2141. 同时运行 N 台电脑的最长时间 - 力扣(LeetCode)

image

using i64 = long long;
class Solution {
public:
    bool check(i64 mid, int num, vector<int>& a)
    {
        i64 sum = 0;
        for (int &x : a)
        {
            if (x > mid) num--;
            else sum += x;
            if (sum >= (i64)num * mid) return true;
        }
        return false;
    }
    long long maxRunTime(int n, vector<int>& batteries) { 
        i64 s = accumulate(batteries.begin(), batteries.end(), 0ll);//必须写0ll,不然会爆int     
        i64 l = -1, r = s + 1;
        while (l + 1 < r)
        {
            i64 mid = l + (r - l >> 1);
            if (check(mid, n, batteries)) l = mid;
            else r = mid;
        }
        return l;
    }
};

贪心优化

using i64 = long long;
class Solution {
public:
    bool check(int mid, int num, vector<int>& a)
    {
        i64 sum = 0;
        for (int &x : a)
        {
            if (x > mid) num--;
            else sum += x;
            if (sum >= (i64)num * mid) return true;
        }
        return false;
    }
    long long maxRunTime(int n, vector<int>& batteries) { 
        i64 s = accumulate(batteries.begin(), batteries.end(), 0ll);//必须写0ll,不然会爆int
        int maxv = *max_element(batteries.begin(), batteries.end());
        if (s > (i64)maxv * n) return s / n;
        //最终范围在[0, maxv]        
        int l = -1, r = maxv + 1;
        while (l + 1 < r)
        {
            int mid = l + (r - l >> 1);
            if (check(mid, n, batteries)) l = mid;
            else r = mid;
        }
        return l;
    }
};

函数单调增加,求最小化答案

待补充

函数单调减少,求最小化答案

image

P3853 [TJOI2007] 路标设置

check中的判断也可以优化掉sum--那步,只需要在除的时候多减一个1就可以,如下图
image

// Problem: P3853 [TJOI2007] 路标设置
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P3853
// Memory Limit: 128 MB
// Time Limit: 1000 ms

#include <algorithm>
#include <iostream>

using namespace std;
using i64 = long long;

const int N = 1e5 + 10;

int d, n, c;
int a[N];

bool check(int mid)
{
    i64 sum = 0;
    for (int i = 2; i <= n; i++)
    {
        if (a[i] - a[i - 1] > mid)
        {
            sum += (a[i] - a[i - 1]) / mid;
            if ((a[i] - a[i - 1]) % mid == 0) sum--;
        }
    }
    return sum <= c;
}

void solve()
{
    cin >> d >> n >> c;
    for (int i = 1; i <= n; i++) cin >> a[i];
    int l = 0, r = d + 1;
    while (l + 1 < r)
    {
        int mid = l + (r - l >> 1);
        if (check(mid)) r = mid;
        else l = mid;
    }
    cout << r;
}

int main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int T = 1;
    while (T--) solve();
    return 0;
}

画匠问题 P1182 数列分段 Section II

image

// Problem: P1182 数列分段 Section II
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P1182
// Memory Limit: 125 MB
// Time Limit: 1000 ms

#include <iostream>

using namespace std;

const int N = 1e5 + 10;

int n, c, s;
int a[N];

// cnt表示满足数组每一段的累加和<=mid需要划分的段数
bool check(int mid)
{
    int cnt = 1, sum = 0;  // cnt为段数,sum为每段的累加和
    for (int i = 0; i < n; i++)
    {
        if (a[i] > mid) return false;  // 如果单点都>mid,那划分几段都不行
        if (sum + a[i] > mid)
        {
            cnt++;
            sum = a[i];
        }
        else sum += a[i];
    }
    return cnt <= c;
}

void solve()
{
    cin >> n >> c;
    for (int i = 0; i < n; i++) cin >> a[i], s += a[i];
    int l = -1, r = s + 1;
    while (l + 1 < r)
    {
        int mid = l + (r - l >> 1);
        if (check(mid)) r = mid;
        else l = mid;
    }
    cout << r;
}

int main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int T = 1;
    while (T--) solve();
    return 0;
}

410. 分割数组的最大值 - 力扣(LeetCode)

同上一题画匠问题

class Solution {
public:
    int n, c;
    bool check(int mid, vector<int>& a)
    {
        int cnt = 1, sum = 0;
        for (int i = 0; i < n; i++)
        {
            if (a[i] > mid) return false;
            if (a[i] + sum > mid)
            {
                cnt++;
                sum = a[i];
            }
            else sum += a[i];
        }
        return cnt <= c;
    }
    int splitArray(vector<int>& nums, int k) {
        n = nums.size(), c = k;
        int s = accumulate(nums.begin(), nums.end(), 0);
        int l = -1, r = s + 1;
        while (l + 1 < r)
        {
            int mid = l + (r - l >> 1);
            if (check(mid, nums)) r = mid;
            else l = mid;
        }
        return r;
    }
};

image

875. 爱吃香蕉的珂珂 - 力扣(LeetCode)

using i64 = long long;
class Solution {
public:
    int c;
    i64 check(int mid, vector<int>& a)
    {
        i64 sum = 0;
        for (int& x : a) sum += (x + mid - 1) / mid;
        return sum <= c;
    }
    int minEatingSpeed(vector<int>& piles, int h) {
        c = h;
        int l = 0, r = *max_element(piles.begin(), piles.end());
        while (l + 1 < r)
        {
            int mid = l + (r - l >> 1);
            if (check(mid, piles)) r = mid;
            else l = mid;
        }
        return r;
    }
};

P1314 [NOIP 2011 提高组] 聪明的质监员

image

// Problem: P1314 [NOIP 2011 提高组] 聪明的质监员
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P1314
// Memory Limit: 125 MB
// Time Limit: 1000 ms

#include <algorithm>
#include <cstring>
#include <iostream>

using namespace std;
using i64 = long long;

const int N = 2e5 + 10;

int n, m;
i64 c, y, sw[N], sv[N], ans = 1e12;
int w[N], v[N], l[N], r[N];

bool check(int mid)
{
    for (int i = 1; i <= n; i++) sw[i] = 0, sv[i] = 0;
    for (int i = 1; i <= n; i++)
    {
        if (w[i] >= mid) sw[i] = sw[i - 1] + 1, sv[i] = sv[i - 1] + v[i];
        else sw[i] = sw[i - 1], sv[i] = sv[i - 1];
    }
    y = 0;
    for (int i = 1; i <= m; i++)
    {
        y += (sw[r[i]] - sw[l[i] - 1]) * (sv[r[i]] - sv[l[i] - 1]);
    }
    ans = min(ans, llabs(c - y));// longlong类型的abs
    return y <= c;
}

void solve()
{
    cin >> n >> m >> c;
    for (int i = 1; i <= n; i++) cin >> w[i] >> v[i];
    for (int i = 1; i <= m; i++) cin >> l[i] >> r[i];
    int l = 0, r = *max_element(w, w + n) + 2;
    while (l + 1 < r)
    {
        int mid = l + (r - l >> 1);
        if (check(mid)) r = mid;
        else l = mid;
    }
    cout << ans;
}

int main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int T = 1;
    while (T--) solve();
    return 0;
}

是否型check函数

P1083 [NOIP 2012 提高组] 借教室

区间前半段都是满足条件的订单,区间后半段是不满足条件的订单,题目问不满足条件的第一个订单的编号

// Problem: P1083 [NOIP 2012 提高组] 借教室
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P1083
// Memory Limit: 128 MB
// Time Limit: 1000 ms

#include <cstring>
#include <iostream>

using namespace std;
using i64 = long long;

const int N = 1e6 + 10;

int n, m;
int r[N], d[N], s[N], t[N];
i64 b[N]; // 差分数组

void add(int l, int r, int c)// 差分操作,让区间[l,r]+c
{
    b[l] += c;
    b[r + 1] -= c;
}

bool check(int mid)
{
    memset(b, 0, sizeof b);
    for (int i = 1; i <= mid; i++) add(s[i], t[i], d[i]);
    for (int i = 1; i <= n; i++)
    {
        b[i] += b[i - 1]; // 差分数组求前缀和得到原数组
        if (b[i] > r[i]) return false;
    }
    return true;
}

void solve()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++) cin >> r[i];
    for (int i = 1; i <= m; i++) cin >> d[i] >> s[i] >> t[i];
    int l = 0, r = m + 1;
    while (l + 1 < r)
    {
        int mid = l + (r - l >> 1);
        if (check(mid)) l = mid;
        else r = mid;
    }
    // l==m说明满足条件的最后一个订单号是第m个订单,由单调性知所有订单满足
    if (l == m) cout << 0 << '\n';
    else cout << -1 << '\n' << l + 1 << '\n';
    // l是最后一个满足的订单编号,l+1才是第一个不满足的订单号
}

int main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int T = 1;
    while (T--) solve();
    return 0;
}

P1902 刺杀大使

二分+BFS。
二分受到的伤害,判断每个伤害是否能走到最后一行,如果能走到,就缩小伤害(r=mid)继续走,找到能走到终点伤害的左边界

image

// Problem: P1902 刺杀大使
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P1902
// Memory Limit: 125 MB
// Time Limit: 1000 ms

#include <cstring>
#include <iostream>
#include <queue>

using namespace std;
using PII = pair<int, int>;

const int N = 1010;

int n, m;
int g[N][N], st[N][N];
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};

bool bfs(int sx, int sy, int mid)
{
    queue<PII> q;
    q.push({sx, sy});
    st[sx][sy] = 1;

    while (q.size())
    {
        auto [x, y] = q.front();
        q.pop();

        for (int i = 0; i < 4; i++)
        {
            int a = x + dx[i], b = y + dy[i];
            if (a < 0 || a >= n || b < 0 || b >= m) continue;
            if (st[a][b] || g[a][b] > mid) continue;
            st[a][b] = 1;
            if (a == n - 1) return true;  // 在这里判断是否到终点
            else q.push({a, b});
        }
    }
    return false;
}

void solve()
{
    cin >> n >> m;
    int l = N, r = 0;
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < m; j++)
        {
            cin >> g[i][j];
            l = min(l, g[i][j]);
            r = max(r, g[i][j]);
        }
    }
    l--, r++;//开区间二分下界-1,上界+1
    while (l + 1 < r)
    {
        int mid = l + (r - l >> 1);
        memset(st, 0, sizeof st);
        if (bfs(0, 0, mid)) r = mid;
        else l = mid;
    }
    cout << r;
}

int main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int T = 1;
    while (T--) solve();
    return 0;
}

二分+DFS写法

// Problem: P1902 刺杀大使
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P1902
// Memory Limit: 125 MB
// Time Limit: 1000 ms

#include <cstring>
#include <iostream>

using namespace std;

const int N = 1010;

int n, m;
int g[N][N], st[N][N];
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};

bool dfs(int x, int y, int mid)
{
    if (x == n - 1) return true;
    st[x][y] = 1;
    for (int i = 0; i < 4; i++)
    {
        int a = x + dx[i], b = y + dy[i];
        if (a < 0 || a >= n || b < 0 || b >= m) continue;
        if (st[a][b] || g[a][b] > mid) continue;
        if (dfs(a, b, mid)) return true;
    }
    return false;
}

void solve()
{
    cin >> n >> m;
    int l = N, r = 0;
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < m; j++)
        {
            cin >> g[i][j];
            l = min(l, g[i][j]);
            r = max(r, g[i][j]);
        }
    }
    l--, r++;  // 开区间二分下界-1,上界+1
    while (l + 1 < r)
    {
        int mid = l + (r - l >> 1);
        memset(st, 0, sizeof st);
        if (dfs(0, 0, mid)) r = mid;
        else l = mid;
    }
    cout << r;
}

int main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int T = 1;
    while (T--) solve();
    return 0;
}

练习题

【算法1-6】二分查找与二分答案

二分查找

P1102 A-B 数对

#include <algorithm>
#include <iostream>

using namespace std;
using i64 = long long;

const int N = 2e5 + 10;

int n, c;
int q[N];

int find1(int x) // 找x第一次出现的位置
{
    int l = -1, r = n;
    while (l + 1 < r)
    {
        int mid = l + (r - l >> 1);
        if (q[mid] >= x) r = mid;
        else l = mid;
    }
    return (r < n && q[r] == x) ? r : -1;
}

int find2(int x) // 找x最后一次出现的位置
{
    int l = -1, r = n;
    while (l + 1 < r)
    {
        int mid = l + (r - l >> 1);
        if (q[mid] <= x) l = mid;
        else r = mid;
    }
    return q[l] == x ? l : -1;
}

void solve()
{
    cin >> n >> c;
    for (int i = 0; i < n; i++) cin >> q[i];
    sort(q, q + n);
    i64 res = 0;
    for (int i = 0; i < n; i++)
    {
        int a = q[i] + c;
        if (find1(a) != -1) res += find2(a) - find1(a) + 1;
        //(结束下标-开始下标+1)==a出现的次数
    }
    cout << res;
}

int main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int T = 1;
    while (T--) solve();
    return 0;
}

P1678 烦恼的高考志愿

// Problem: P1678 烦恼的高考志愿
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P1678
// Memory Limit: 128 MB
// Time Limit: 1000 ms

#include <algorithm>
#include <iostream>

using namespace std;
using i64 = long long;
using PII = pair<int, int>;

const int N = 1e5 + 10;

int n, m;
int a[N], b[N];

int find(int x) // 在a[i]中找到第一个>=x的数的下标
{
    int l = -1, r = m;
    while (l + 1 < r)
    {
        int mid = l + (r - l >> 1);
        if (a[mid] >= x) r = mid;
        else l = mid;
    }
    return r;
}

void solve()
{
    cin >> m >> n;
    for (int i = 0; i < m; i++) cin >> a[i];
    for (int i = 0; i < n; i++) cin >> b[i];
    i64 res = 0;
    sort(a, a + m);
    for (int i = 0; i < n; i++)
    {
        int k = find(b[i]);// 在a[i]中找到第一个>=b[i]的数的下标
        if (k == 0) res += a[0] - b[i];// 第一个数
        else if (k == m) res += b[i] - a[m - 1];// 最后一个数
        else res += min(abs(b[i] - a[k - 1]), abs(b[i] - a[k])); // 中间的数
    }
    cout << res;
}

int main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int T = 1;
    while (T--) solve();
    return 0;
}

P1571 眼红的Medusa

// Problem: P1571 眼红的Medusa
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P1571
// Memory Limit: 125 MB
// Time Limit: 1000 ms

#include <algorithm>
#include <iostream>

using namespace std;

const int N = 1e5 + 10;

int n, m;
int a[N], b[N];

int find(int x)
{
    int l = -1, r = m;
    while (l + 1 < r)
    {
        int mid = l + (r - l >> 1);
        if (b[mid] >= x) r = mid;
        else l = mid;
    }
    return r;
}

void solve()
{
    cin >> n >> m;
    for (int i = 0; i < n; i++) cin >> a[i];
    for (int i = 0; i < m; i++) cin >> b[i];
    sort(b, b + m);
    for (int i = 0; i < n; i++)
    {
        int k = find(a[i]);
        if (k < m && b[k] == a[i]) cout << a[i] << ' ';
    }
}

int main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int T = 1;
    while (T--) solve();
    return 0;
}

二分答案

P1918 保龄球

P8814 [CSP-J 2022] 解密

P3184 [USACO16DEC] Counting Haybales S

P2920 [USACO08NOV] Time Management S

P1577 切绳子

P1843 奶牛晒衣服

P2985 [USACO10FEB] Chocolate Eating S

P2810 Catch the theives

posted @   Tshaxz  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
Language: HTML
点击右上角即可分享
微信分享提示