【专题总结】二分查找(持续更新)

本文详细介绍了二分查找算法在解决实际问题中的应用,通过多个竞赛题目的解题过程,阐述了如何利用二分查找优化时间复杂度,将原本线性的时间复杂度优化到对数级别。涉及的题目包括POJ 2456、POJ 1064、Codeforces 689C和689D等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

假如我们遇到了这样的问题,问题要求我们在 y=f(x),(axb) 中寻找符合条件的 y ,其中 f 可以为显式函数或隐式关系。一种朴素的思路是在取值范围内枚举所有的 x ,然后判断 y 是否满足条件。这样的时间复杂度是 O(ba) 。但是当 ba 的值很大时(例如 107 )就太耗时了。此时若 y 能满足“二分性”(例如升序,降序),则每次在怀疑区间 [l,r] 内取中点 xmid (初始时 l=a,r=b ),再通过判断 f(xmid) 是否满足条件来修正(缩小)怀疑区间(更新 lr )。这样就可以将时间复杂度优化到 O(log(ba))


POJ 2456

大意

一维坐标轴上有 n 个位置,我们要把 c 个牛放在 n 个位置上。有许多方案可以放置这些牛。定义某个方案 s 中任意两只牛之间距离的最小值为 d[s] ,求对于所有的方案 sd[s] 的最大值。

思路

  • 首先,在 k 为定值的情况下,判断在 d[s]=k 的情况下是否存在 s 是比较容易的(我们可以尝试在某个位置上放牛,同时判断在这个位置上放牛是否与前一只被放置的牛冲突,若不冲突的话就可以放,否则不行)。那么我们就可以枚举每个 k ,在判断是否存在 s 的情况下更新最优解。
  • 其次,如果真的枚举的话时间复杂度太大了。实际上,我们没有好好利用 k 的“二分性”。对于给定的k_0,如果存在对应的情况 s 的话,说明现在至少知道 k 是满足条件的,所以小于 k 的最小距离就不用管了。如果不存在对应的情况 s 的话,大于 k 的最小距离也不用管了。
  • 最后,我们对怀疑区间 [1,1,000,000,000] 进行二分查找,查找出最优解即可。

代码

#include <cstdio>
#include <algorithm>
using namespace std;

const int maxn = 1e5 + 10, INF = 1e9;
int n, c, l, r, mid, x[maxn];

bool ok(int d) {
    int cnt = 1, last = 0;
    for(int i = 1; i < n; i++) {
        if(x[i] - x[last] >= d) {
            last = i;
            cnt++;
        }
    }
    return cnt >= c;
}

int main() {
    scanf("%d%d", &n, &c);
    for(int i = 0; i < n; i++) {
        scanf("%d", &x[i]);
    }
    sort(x, x + n);
    l = 1;
    r = INF + 1;
    while(r - l > 1) {
        mid = (l + r) >> 1;
        if(ok(mid)) {
            l = mid;
        }
        else {
            r = mid;
        }
    }
    printf("%d\n", l);
    return 0;
}

POJ 1064

大意

n 根长度各异的电缆,现要把这些电缆切割成等长的 k 份,设切割后每根电缆的长度都是 d ,问 d 的最大值是多少。

思路

  • 每种切割方案对应一个 d ,因此我们可以枚举 d ,判断切割方案是否合法的同时更新最优值。
  • 但是这样做枚举量太大了。我们发现 d 是呈现“二分性”的,也就是对于 d0 ,知道了 d0 符合条件后任意小于或等于 d0d 都满足条件。知道了 d0 不满足条件后,任意大于 d0d 都是不满足条件的。
  • 于是我们对解区间进行二分查找,找到最优的 d

代码

#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;

const int INF = 1e9 + 10;
const int maxn = 1e4 + 10;
int n, k;
double l, r, m, mid, c[maxn];

bool ok(double x) {
    int cnt = 0;
    for(int i = 0; i < n; i++) {
        cnt += (int)(c[i] / x);
    }
    return cnt >= k;
}

int main() {
    scanf("%d%d", &n, &k);
    for(int i = 0; i < n; i++) {
        scanf("%lf", &c[i]);
    }
    sort(c, c + n);
    l = 0;
    r = INF;
    for(int i = 0; i < 100; i++) {
        mid = (l + r) / 2;
        if(ok(mid)) {
            l = mid;
        }
        else {
            r = mid;
        }
    }
    printf("%.2f\n", floor(100 * l) / 100);
    return 0;
}

POJ 3104

大意

思路

代码


POJ 3111

大意

思路

代码


POJ 3579

大意

思路

代码


Codeforces 689C

大意

小偷们有一个容量为 n 背包,他们偷窃的 4 个物品的重量经排序后定能成为一个等比数列,但是其总和也必须不超过背包的容量。现给出小偷们偷东西的方案总数,求可能的背包容量的最小值。

思路

  • m 得知 n 是困难的,但是从 n 得知 m 却是可行的。根据计数方法,将 n 代入下面的 c 函数中就能求出 m 。于是我们可以枚举所有可能的 n ,当结果等于 m 的时候用当前 n 更新最优解(此处是最小值)就好了。
  • 但是枚举的话太费时。因此我们利用 m 的“二分性”( m 这里是对 n 的升序)来对 n 进行二分查找,就能在短时间内找到答案。

代码

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
ll m, l, r, mid;

ll c(ll n) {
    ll res = 0;
    for(ll q = 2; n >= q * q * q; q++) {
        res += n / (q * q * q);
    }
    return res;
}

int main() {
    cin >> m;
    l = 7;
    r = 100 * m;
    while(r - l > 1) {
        mid = (l + r) >> 1;
        if(c(mid) >= m) {
            r = mid;
        }
        else {
            l = mid;
        }
    }
    if(c(r) == m) {
        cout << r << endl;
    }
    else {
        cout << -1 << endl;
    }
    return 0;
}

Codeforces 689D

大意

题目给出两个数组 ab ,求满足 max([al,ar])=min([bl,br]) 的二元组 (l,r) 的个数。

思路

  • 我们定义 max(i,j)a[i]a[j] 之间的最大值。那么 max 函数有这样的性质:当 i 为定值的时候, max(i,j) 随着 j 的增大而非减。同样, min(i,j) 随着j的增大而非增。也就是说 max(i,j)min(i,j)i 为定值的情况下对于 j 是非减函数。有了“序”以后一切都好办了。我们相当于找到了 max(i,j)min(i,j) 的二分性。于是对于每个 i ,我们可以枚举 j ,找出满足 max(i,j)min(i,j) 的连续的 j1,...,jk ,那么对于当前的 i 而言,满足 max(i,j)min(i,j)j 的个数就是 jkj1+1j 的合法区间的长度),我们将其加到答案 ans 中。枚举完 i 以后, ans 就是最终答案了。
  • 其中,根据二分性我们将对于 j 的枚举改成对于 j 的二分查找能够将时间复杂度控制在合理的范围内。另外快速查询 max(i,j)min(i,j) 可以用 ST 表(查询复杂度 O(1) )或线段树(查询复杂度 O(logn) )这样的数据结构,否则仍然会超时。

代码

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const int maxn = 2e5 + 10;
int n, lb, ub, a[maxn], b[maxn];
ll ans;

struct table {
    int stTable[maxn][32][2];
    int preLog2[maxn];
    void init(int n, int* array) {
        preLog2[1] = 0;
        for(int i = 2; i <= n; i++) {
            preLog2[i] = preLog2[i-1];
            if((1 << preLog2[i] + 1) == i) {
                ++preLog2[i];
            }
        }
        for(int i = n - 1; i >= 0; i--) {
            stTable[i][0][0] = stTable[i][0][1] = array[i];
            for(int j = 1; (i + (1 << j) - 1) < n; j++) {
                stTable[i][j][0] = max(stTable[i][j-1][0], stTable[i+(1<<j-1)][j-1][0]);
                stTable[i][j][1] = min(stTable[i][j-1][1], stTable[i+(1<<j-1)][j-1][1]);
            }
        }
    }
    int rmq(int l, int r, int d) {
        int len = r - l + 1, k = preLog2[len];
        if(d == 0) {
            return max(stTable[l][k][0], stTable[r-(1<<k)+1][k][0]);
        }
        else {
            return min(stTable[l][k][1], stTable[r-(1<<k)+1][k][1]);
        }
    }
}A, B;

int binary(int L, int& lb, int& ub) {
    int l = L - 1, r = n;
    while(r - l > 1) {
        int mid = (l + r) >> 1;
        int tmp = A.rmq(L, mid, 0) - B.rmq(L, mid, 1);
        if(tmp < 0) {
            l = mid;
        }
        else {
            r = mid;
        }
    }
    lb = r;
    l = L - 1, r = n;
    while(r - l > 1) {
        int mid = (l + r) >> 1;
        int tmp = A.rmq(L, mid, 0) - B.rmq(L, mid, 1);
        if(tmp <= 0) {
            l = mid;
        }
        else {
            r = mid;
        }
    }
    ub = r;
}

int main() {
    scanf("%d", &n);
    for(int i = 0; i < n; i++) {
        scanf("%d", &a[i]);
    }
    A.init(n, a);
    for(int i = 0; i < n; i++) {
        scanf("%d", &b[i]);
    }
    B.init(n, b);
    for(int L = 0; L < n; L++) {
        binary(L, lb, ub);
        ans += ub - lb;
    }
    printf("%I64d\n", ans);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值