Loading

二分

二分

二分:用来查找某类性质的数。

注意事项:边界问题。

模板

整数二分

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;
}

对于第二个模板,如果 l = mid, mid 要加 1,因为当 r = l - 1 时,int向下取整,后面 l = mid = l 会造成死循环。

模板 1 通常用来查找大于等于 \(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;
}

查找

查找 - 洛谷

查找单调不减序列中某个数 \(x\) 首次出现的位置。

思路:查找满足大于等于 \(x\) 的数,用模板 1;

#include <iostream>
using namespace std;

const int N = 1000010;

int a[N];

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

烦恼的高考志愿

现有 \(m(m\le100000)\) 所学校,每所学校预计分数线是 \(a_i(a_i\le10^6)\)。有 \(n(n\le100000)\) 位学生,估分分别为 \(b_i(b_i\le10^6)\)

根据 \(n\) 位学生的估分情况,分别给每位学生推荐一所学校,要求学校的预计分数线和学生的估分相差最小(可高可低,毕竟是估分嘛),这个最小值为不满意度。求所有学生不满意度和的最小值。

思路: 将学校分数线从小到大排序,对于每位学生的估分,用二分在 \(a\) 里找到大于等于该估分的最小值的下标 \(l\),因为找到的是大于等于 \(x\) 的最小值,\(a[l]\)\(a[l-1]\) 肯定是跟 \(x\) 最接近的值,求出最小的不满意度即可。最后特判一下边界条件

#include <algorithm>
#include <iostream>

using namespace std;
const int N = 100010;

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

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    for (int i = 1; i <= m; i++) scanf("%d", &b[i]);
    sort(a + 1, a + n + 1);
    for (int i = 1; i <= m; i++)
    {
        int x = b[i];
        int l = 0, r = n + 1, mid;
        while (l < r)
        {
            mid = l + r >> 1;
            if (a[mid] >= x) r = mid;
            else l = mid + 1;
        }
        if (a[1] >= x) ans += a[1] - x;
        else if (a[n] <= x) ans += x - a[n];
        else ans += min(a[l] - x, x - a[l - 1]);
    }
    cout << ans << endl;
    return 0;
}

二分答案

枚举答案,看答案能否满足题目要求。

银行贷款

当一个人从银行贷款后,在一段时间内他(她)将不得不每月偿还固定的分期付款。这个问题要求计算出贷款者向银行支付的利率。假设利率按月累计。

三个用空格隔开的正整数。

第一个整数表示贷款的原值,第二个整数表示每月支付的分期付款金额,第三个整数表示分期付款还清贷款所需的总月数。

枚举利率,看能否在总月数之内把贷款变为 \(0\)

#include <iostream>
#include <cmath>

using namespace std;

const double eps = 1e-4;
int n, t, m;

bool check(double x)
{
    double res = n;
    for (int i = 1; i <= m; i++)
        res = res + res * x / 100 - t;
    return res >= 0;
}

int main()
{
    cin >> n >> t >> m;

    double l = 0, r = 1000, mid;
    while (r - l > eps)
    {
        mid = (l + r) / 2;
        if (check(mid))
            r = mid;
        else
            l = mid;
    }
    printf("%.1lf\n", l);
    return 0;
}

砍树

伐木工人 Mirko 需要砍 \(M\) 米长的木材。对 Mirko 来说这是很简单的工作,因为他有一个漂亮的新伐木机,可以如野火一般砍伐森林。不过,Mirko 只被允许砍伐一排树。

Mirko 的伐木机工作流程如下:Mirko 设置一个高度参数 \(H\)(米),伐木机升起一个巨大的锯片到高度 \(H\),并锯掉所有树比 \(H\) 高的部分(当然,树木不高于 \(H\) 米的部分保持不变)。Mirko 就得到树木被锯下的部分。例如,如果一排树的高度分别为 \(20,15,10\)\(17\),Mirko 把锯片升到 \(15\) 米的高度,切割后树木剩下的高度将是 \(15,15,10\)\(15\),而 Mirko 将从第 \(1\) 棵树得到 \(5\) 米,从第 \(4\) 棵树得到 \(2\) 米,共得到 \(7\) 米木材。

Mirko 非常关注生态保护,所以他不会砍掉过多的木材。这也是他尽可能高地设定伐木机锯片的原因。请帮助 Mirko 找到伐木机锯片的最大的整数高度 \(H\),使得他能得到的木材至少为 \(M\) 米。换句话说,如果再升高 \(1\) 米,他将得不到 \(M\) 米木材。

check 检查 \(x\) 能否满足得到的木材满足 \(M\) 米,为了使树木尽可能高,满足条件就向右区间查找。

#include <iostream>
using namespace std;

const int N = 1000010;

int n, m;
int a[N];

bool check(int x)
{
    LL res = 0;
    for (int i = 0; i < n; i++) res += max(0, a[i] - x);
    return res >= m;
}

int main()
{
    cin >> n >> m;
    for (int i = 0; i < n; i++) scanf("%d", &a[i]);
    int l = 0, r = 2e9, mid;
    while (l < r)
    {
        mid = l + r + 1 >> 1;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    cout << l << endl;
    return 0;
}

跳石头

一年一度的“跳石头”比赛又要开始了!

这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石。组委会已经选择好了两块岩石作为比赛起点和终点。在起点和终点之间,有 \(N\) 块岩石(不含起点和终点的岩石)。在比赛过程中,选手们将从起点出发,每一步跳向相邻的岩石,直至到达终点。

为了提高比赛难度,组委会计划移走一些岩石,使得选手们在比赛过程中的最短跳跃距离尽可能长。由于预算限制,组委会至多从起点和终点之间移走 \(M\) 块岩石(不能移走起点和终点的岩石)。

输入:

第一行包含三个整数 \(L,N,M\),分别表示起点到终点的距离,起点和终点之间的岩石数,以及组委会至多移走的岩石数。保证 \(L \geq 1\)\(N \geq M \geq 0\)

接下来 \(N\) 行,每行一个整数,第 \(i\) 行的整数 \(D_i( 0 < D_i < L)\), 表示第 \(i\) 块岩石与起点的距离。这些岩石按与起点距离从小到大的顺序给出,且不会有两个岩石出现在同一个位置。

求最短跳跃距离的最大值。

找小于等于某数的最大值,考虑模板 2。

每次枚举答案,然后检查在该答案下需要移走的岩石数能否满足答案要求,能满足要使答案尽可能大,就接着向右半区间查找。

#include <iostream>
using namespace std;

const int N = 50010;

int d[N];
int l, n, m;

bool check(int x)
{
    int cnt = 0, pre = 0;
    for (int i = 1; i <= n + 1; i++)
    {
        if (d[i] - d[pre] < x)
        {
            cnt++;
            continue;
        }
        pre = i;
    }
    return cnt <= m;
}
int main()
{
    scanf("%d%d%d", &l, &n, &m);
    for (int i = 1; i <= n; i++) scanf("%d", &d[i]);
    d[n + 1] = l;
    int l = 1, r = 1e9, mid;
    while (l < r)
    {
        mid = l + r + 1 >> 1;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    cout << l << endl;
    return 0;
}

数列分段 Section

对于给定的一个长度为N的正整数数列 \(A_{1\sim N}\),现要将其分成 \(M\)\(M\leq N\))段,并要求每段连续,且每段和的最大值最小。

关于最大值最小:

例如一数列 \(4\ 2\ 4\ 5\ 1\) 要分成 \(3\) 段。

将其如下分段:

\[[4\ 2][4\ 5][1] \]

第一段和为 \(6\),第 \(2\) 段和为 \(9\),第 \(3\) 段和为 \(1\),和最大值为 \(9\)

将其如下分段:

\[[4][2\ 4][5\ 1] \]

第一段和为 \(4\),第 \(2\) 段和为 \(6\),第 \(3\) 段和为 \(6\),和最大值为 \(6\)

并且无论如何分段,最大值不会小于 \(6\)

所以可以得到要将数列 \(4\ 2\ 4\ 5\ 1\) 要分成 \(3\) 段,每段和的最大值最小为 \(6\)

问题:求每段和最大值最小为多少。

求大于某数的最小值,用模板 1。枚举答案,看在该答案下需要分的区间能否满足要求,达到要求就向左半区间查找。

#include <algorithm>
#include <iostream>

using namespace std;

typedef long long LL;
const int N = 100010;

int a[N];
int n, m;

bool check(int x)
{
    if (a[n] > x) return false;
    int cnt = 0, t = 0;
    for (int i = 1; i <= n; i++)
    {
        if (a[i] > x) return false;
        if (t + a[i] <= x) t += a[i];
        else
        {
            cnt++;
            t = a[i];
        }
    }
    cnt++;
    return cnt <= m;
}

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    int l = 0, r = 1e9, mid;
    while (l < r)
    {
        mid = l + ((r - l) >> 1);
        if (check(mid)) r = mid;
        else l = mid + 1;
    }
    cout << l << endl;
    return 0;
}

kotori的设备

kotori 有 \(n\) 个可同时使用的设备。

\(i\) 个设备每秒消耗 \(a_i\) 个单位能量。能量的使用是连续的,也就是说能量不是某时刻突然消耗的,而是匀速消耗。也就是说,对于任意实数,在 \(k\) 秒内消耗的能量均为 \(k\times a_i\) 单位。在开始的时候第 \(i\) 个设备里存储着 \(b_i\) 个单位能量。

同时 kotori 又有一个可以给任意一个设备充电的充电宝,每秒可以给接通的设备充能 \(p\) 个单位,充能也是连续的,不再赘述。你可以在任意时间给任意一个设备充能,从一个设备切换到另一个设备的时间忽略不计。

kotori 想把这些设备一起使用,直到其中有设备能量降为 \(0\)。所以 kotori 想知道,在充电器的作用下,她最多能将这些设备一起使用多久。

输入格式

第一行给出两个整数 \(n,p\)

接下来 \(n\) 行,每行表示一个设备,给出两个整数,分别是这个设备的 \(a_i\)\(b_i\)

输出格式

如果 kotori 可以无限使用这些设备,输出 \(-1\)

否则输出 kotori 在其中一个设备能量降为 \(0\) 之前最多能使用多久。

设你的答案为 \(a\),标准答案为 \(b\),只有当 \(a,b\) 满足
\(\dfrac{|a-b|}{\max(1,b)} \leq 10^{-4}\) 的时候,你能得到本测试点的满分。

#include <iostream>
#include <vector>

using namespace std;

const int N = 100010;

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

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

int main()
{
    scanf("%d%d", &n, &p);
    bool flag = true;
    int sum = 0;
    for (int i = 1; i <= n; i++)
    {
        scanf("%d%d", &a[i], &b[i]);
        if (flag)
        {
            sum += a[i];
            if (sum > p)
                flag = false;
        }
    }
    if (p >= sum)
    {
        puts("-1");
        return 0;
    }
    double l = 0, r = 1e10, mid;
    while (r - l > 1e-6)
    {
        mid = (l + r) / 2;
        if (check(mid)) l = mid;
        else r = mid;
    }
    cout << l << endl;
    return 0;
}

路标设置

题目背景

B 市和 T 市之间有一条长长的高速公路,这条公路的某些地方设有路标,但是大家都感觉路标设得太少了,相邻两个路标之间往往隔着相当长的一段距离。为了便于研究这个问题,我们把公路上相邻路标的最大距离定义为该公路的“空旷指数”。

题目描述

现在政府决定在公路上增设一些路标,使得公路的“空旷指数”最小。他们请求你设计一个程序计算能达到的最小值是多少。请注意,公路的起点和终点保证已设有路标,公路的长度为整数,并且原有路标和新设路标都必须距起点整数个单位距离。

输入格式

\(1\) 行包括三个数 \(L,N,K\),分别表示公路的长度,原有路标的数量,以及最多可增设的路标数量。

\(2\) 行包括递增排列的 \(N\) 个整数,分别表示原有的 \(N\) 个路标的位置。路标的位置用距起点的距离表示,且一定位于区间 \([0,L]\) 内。

输出格式

输出 \(1\) 行,包含一个整数,表示增设路标后能达到的最小“空旷指数”值。

#include <iostream>
using namespace std;

const int N = 100010;

int a[N];
int L, n, k;

bool check(int d)
{
    int cnt = 0, pre = 0;
    for (int i = 1; i <= n; i++)
    {
        if (a[i] - pre <= d)
        {
            pre = a[i];
            continue;
        }
        pre += d;
        cnt++;
        i--;
    }
    return cnt <= k;
}

int main()
{
    scanf("%d%d%d", &L, &n, &k);
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    int l = 0, r = L, mid;
    while (l < r)
    {
        mid = l + r >> 1;
        if (check(mid)) r = mid;
        else l = mid + 1;
    }
    cout << l << endl;
    return 0;
}
posted @ 2022-08-10 21:01  一叶知秋`  阅读(312)  评论(0)    收藏  举报