二分答案

综述: 答案有一个区间, 在区间中二分找到最优答案
适用于: 数据范围较大, 答案具有单调性
题目特点:

  1. 在一个区间内, 区间范围很大 暴力会超时
  2. 直接搜索不好搜, 但是可以判断某个答案行不行
  3. 单调性, 区间中的值越大, 题目中某个量对应会变大/小
  4. 求…最大值的最小, 最小值的最大

在这里插入图片描述

基本模版

while (l < r)
{
    int mid = l + r >> 1;	
    if (check(mid))  
        r = mid;   
    else 
        l = mid + 1;
}

往左找答案, 求最小, mid 不用 + 1, r = mid, l + 1
往右找答案, 求最大, mid + 1, l = mid, r - 1

while (l < r)
{
    int mid = l + r + 1 >> 1;	
    if (check(mid))  
        l = mid;
    else 
        r = mid - 1;
}

浮点除法不会取整, 不用 +1 或-1

while (r - l > 1e - 5) // 需要一个精度保证
{
    double mid = (l + r) / 2;
    if (check(mid))
        l = mid; // 或r=mid;
    else
        r = mid; // 或l=mid;
}

A-B 数对

给出一串正整数数列以及一个正整数 C C C,要求计算出所有满足 A − B = C A - B = C AB=C 的数对的个数(不同位置的数字一样的数对算不同的数对)。
正整数中包含的满足 A − B = C A - B = C AB=C 的数对的个数。

lower_bound / upper_bound

取得a数组中最小数的下标

lower_bound(a,a+n,x)-a      //下标从0开始
lower_bound(a+1,a+n+1,x)-a  //下标从1开始
upper_bound(a,a+n,x)-a      //下标从0开始
upper_bound(a+1,a+n+1,x)-a  //下标从1开始
#include <bits/stdc++.h>
using namespace std;
long a[200001];
long N, C, ans;
int main()
{
    cin >> N >> C;
    for (int i = 1; i <= N; i++)
    {
        cin >> a[i];
    }
    sort(a + 1, a + N + 1);
    for (int i = 1; i <= N; i++)
    {
        ans += ((upper_bound(a + 1, a + N + 1, a[i] + C) - a) - (lower_bound(a + 1, a + N + 1, a[i] + C) - a));
    }
    cout << ans;
    return 0;
}

二分

给出了C,我们要找出A和B。我们可以遍历数组,即让每一个值先变成B,然后二分找对应的A首次出现位置,看是否能找到。

如果找到A,那就二分找最后出现的位置,继而,求出A的个数,即数对的个数。

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

const int N = 200010;
long long a[N], n, c, cnt, st;

int main()
{
    cin >> n >> c;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    sort(a + 1, a + 1 + n); // 先排序

    for (int i = 1; i < n; i++) // 遍历每一个B
    {
        int l = i + 1, r = n; // 寻找A第一次出现的位置,使得A-B=C
        while (l < r)         // 因为是第一次出现,尽量往左,模板1
        {
            int mid = l + r >> 1;
            if (a[mid] - a[i] >= c)
                r = mid; // 判断:在目标值的右边,满足,往左来
            else
                l = mid + 1;
        }
        if (a[l] - a[i] == c)
            st = l; // 能找到C就继续
        else
            continue;

        l = st - 1, r = n; // 查找A最后出现的位置
        while (l < r)      // 因为是最后一次出现,尽量往右,模板2
        {
            int mid = l + r + 1 >> 1;
            if (a[mid] <= a[st])
                l = mid; // 判断:在目标值的左边,满足,往右去
            else
                r = mid - 1;
        }
        cnt += l - st + 1; // 最后出现的位置减首次出现的位置就是区间长度,即A的个数
    }
    cout << cnt;
    return 0;
}

分巧克力

儿童节那天有 K K K 位小朋友到小明家做客。小明拿出了珍藏的巧克力招待小朋友们。

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

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

  1. 形状是正方形,边长是整数。

  2. 大小相同。

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

读题 => 每块巧克力可以切得块数为 (H / mid) * (w / mid)

从d = 1开始一个一个试, 会超时
采取二分, 从1, 100000, 开始试

(H[i]W[i]) / (xx) 会出现 1x8的巧克力裁不出2x2的巧克力,但(H[i]W[i]) / (xx)会认为这块巧克力可以裁成2块2x2的巧克力

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f
#define endl "\n"
#define MAX 100001
int h[MAX], w[MAX];
int  n, k;
int Fun(int x)
{
    int sum = 0;
    for (int i = 1; i <= n; i++)
        sum += (h[i] / x) * (w[i] / x);
    if (sum >= k)
        return 1;
    return 0;
}
void solve()
{
    int i;
    cin >> n >> k;
    for (i = 1; i <= n; i++)
        cin >> h[i] >> w[i];
    int left = 1, right = MAX, mid;
    while (left < right)
    {
        mid = (left + right + 1) / 2;
        if (Fun(mid))
            left = mid;
        else
            right = mid - 1;
    }
    cout << left;
}

signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);

    solve();
    return 0;
}

子序列的平均值

给定一个长度为n的非负序列A,请你找出一个长度不小于L的子段(子段是序列A中一些连续的元素构成的集合),使得子段中数值的平均值最大。最终输出这个最大的平均值。

输入格式:
第一行两个整数n,L(1<=L<=n<=100,000)

以下n行,每行一个非负整数,表示序列A中每个元素的值。

思路

答案一定位于最小值和最大值之间

check的标准: 序列和 / L 如果大于mid,说明平均值可以更大l = mid;
如果小于mid,说明平均值不够这么大r = mid;

代码

#include <iostream>
#include <cmath>
using namespace std;

int n = 0, L = 0;
double a[100001] = {0};

bool Check(double average)
{
    bool flag = false;
    double sum[100001] = {0};
    for (int i = 1; i <= n; ++i)
    {
        sum[i] = sum[i - 1] + (a[i] - average);
    }
    double fronts = 1e9;
    for (int i = L; i <= n; ++i)
    {
        fronts = fmin(fronts, sum[i - L]);
        if (sum[i] - fronts > 0)
        {
            flag = true;
            break;
        }
    }
    return flag;
}
int main()
{
    cin >> n >> L;
    double max = 0, min = 1e9;

    for (int i = 1; i <= n; ++i)
    {
        cin >> a[i];
        max = fmax(a[i], max);
        min = fmin(a[i], min);
    }

    double low = min, high = max, answer = (low + high) / 2;
    while (high - low > 1e-5)
    {
        if (Check(answer))
            low = answer;
        else
            high = answer;
        answer = (low + high) / 2;
    }
    cout << (int)(high * 1000) << endl;
}

伐木工人

伐木工人 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 米木材。

【输入格式】:
第 1 行 2 个整数 N 和 M,N 表示树木的数量,M 表示需要的木材总长度。

第 2 行 N 个整数表示每棵树的高度。

【输出格式】:
1 个整数,表示锯片的最高高度

思路

求一个最大高度,使得砍到的树的总长度满足条件
在这里插入图片描述
在这里插入图片描述

大于 ans 的高度,不满足条件,小于 ans 的高度,都满足条件.

  1. 确定答案所在的区间
  2. 判断 mid 是否满足条件,更新答案区间
  3. 得到符合题目要求的答案

①要求满足条件的最大高度,那么首先答案介于0-树的最大高度Heigt.

②用二分查找的框架去判断 mid符合条件,然后根据判断结果去更新答案区间

得到的总长度sum ≥ m说明高度低了->left变为mid + 1;
否则 right 变为 mid - 1;

int left = 0, right = height, mid, ans;
while (left <= right)
{ 
    mid = (left + right) / 2;
    long long sum = 0; 
    for (i = 1; i <= n; i++)
    {
        if (a[i] > mid)
            sum += a[i] - mid;
    }
   
    if (sum >= m)
        ans = mid, left = mid + 1;
    else
        right = mid - 1;
}

代码

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

int n, m, a[1000005], height;
int Binary_answer(int h)
{
    int left = 0, right = h, mid, ans;
    while (left <= right)
    { 
        mid = (left + right) / 2;
        long long sum = 0; 
        for (int i = 1; i <= n; i++)
        {
            if (a[i] > mid)
                sum += a[i] - mid;
        }
        if (sum >= m)
            ans = mid, left = mid + 1;
        else
            right = mid - 1;
    }
    return ans;
}
int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        if (a[i] > height)
            height = a[i];
    }
    cout << Binary_answer(height);
    return 0;
}

满足条件的最大(小)值 - 木材加工

木材厂有
n 根原木,现在想把这些木头切割成 k 段长度均为 l 的小段木头(木头有可能有剩余)。

当然,我们希望得到的小段木头越长越好,请求出 l 的最大值
例如有两根原木长度分别为 11 和 21要求切割成等长的 6 段,很明显能切割出来的小段木头长度最长为 5。

代码

#include <bits/stdc++.h>
using namespace std;
int n, m, a[100005], height;
long long sum;
int Binary_answer(int h)
{
    int left = 0, right = h, mid, ans;
    while (left <= right)
    {
        mid = (left + right) / 2;
        int sum1 = 0;
        for (int i = 1; i <= n; i++)
        {
            sum1 += a[i] / mid; // 计算长度为mid时能切割的段数
        }
        // 判断大小,更新区间
        if (sum1 >= m)
            ans = mid, left = mid + 1;
        else
            right = mid - 1;
    }
    return ans;
}
int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        sum += a[i];                // 求出木材的总和
        height = max(height, a[i]); // 找到所有木材中的最大值
    }
    if (sum < m)
    { // 如果总和小于m,说明连长度切1厘米都无法切m段
        cout << 0;
        return 0;
    }
    // 二分答案
    cout << Binary_answer(height);
    return 0;
}

最大值最小化问题 - 数列分段

对于给定的一个长度为N的正整数数列 A 1 ∼ N A _{1∼N}A
1∼N.
现要将其分成 M(M≤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. 确定最大值所在的区间,left 和 right
  2. 二分判断最大值为 mid 时数列是否可以分成 m 段
  3. 根据判断结果更新区间
  4. 得到符合题目要求的答案

①完成第一步,确定区间。要求最大值最小,则该值必定是大于数列中的最大值的(最大值单独为一段的时候),最大值则为所有数列之和​。所以最大值的区间 left=数列中的最大值,right=数列的所有数相加之和,则:

int n,m,a[100005];
cin >>  n >> m;
for(i = 1; i <= n; i++)
{
	cin >> a[i];
	sum += a[i];//sum为答案右边界 
	mx = max(mx,a[i]); //mx为答案左边界 
} 

②二分判断最大值为 mid 时,数列是否可以分成 m 段,采用贪心策略,分割的每一段的总和尽量接近但不超过 mid​,则:

while (left <= right)
{
    mid = (left + right) / 2;
    // 计算最大值为mid时,能分割的段数
    int sum1 = 0, cnt = 1; // 分别表示当前这一段的数字总和、数列目前段数
    for (int i = 1; i <= n; i++)
    {
        // 判断a[i]是否可以连接到当前这段数字中
        if (sum1 + a[i] <= mid)
            sum1 += a[i]; // sum+a[i]不超过mid则可以继续连接
        else
            sum1 = a[i], cnt++; // 否则a[i]自起一段,数列总段数cnt+1
    }
}

③根据判断结果更新区间,因为是求最大值的最小,所以区间上是向左找的。
当 cnt<=m 时,说明 mid 值偏大,此时去查看更小的,即 ans=mid,right=mid-1,如果分割的段数超过 m 段,说明 mid 要更大一点,即 left=mid+1。

// 判断结果,更新区间
if (cnt <= m)
    ans = mid, right = mid - 1;
else
    left = mid + 1;

④得到符合题目要求的答案:

printf("%d",ans);

代码

#include<bits / stdc++.h>
using namespace std;
int n, m, a[100005];
int sum, mx;
int Binary_answer(int l, int r)
{
    int left = l, right = r, mid, ans;
    while (left <= right)
    {
        mid = (left + right) / 2;
        // 计算最大值为mid时,能分割的段数
        int sum1 = 0, cnt = 1; // 分别表示当前这一段的数字总和、数列目前段数
        for (int i = 1; i <= n; i++)
        {
            // 判断a[i]是否可以连接到当前这段数字中
            if (sum1 + a[i] <= mid)
                sum1 += a[i]; // sum+a[i]不超过mid则可以继续连接
            else
                sum1 = a[i], cnt++; // 否则a[i]自起一段,数列总段数cnt+1
        }
        // 判断结果,更新区间
        if (cnt <= m)
            ans = mid, right = mid - 1;
        else
            left = mid + 1;
    }
    return ans;
}
int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        sum += a[i];        // sum为答案右边界
        mx = max(mx, a[i]); // mx为答案左边界
    }
    // 二分答案
    cout << Binary_answer(mx, sum);
    return 0;
}

最小值最大化问题 - 进击的奶牛

Farmer John 建造了一个有 N(2≤ N ≤100000)个隔间的牛棚,这些隔间分布在一条直线上,坐标是 x 1 , . . . , x N ( 0 ≤ x i ≤ 1000000000 ) x_1,…,x_N(0 ≤ x_i≤1000000000)x 1,…,x
他的 C(2≤ C≤ N)头牛不满于隔间的位置分布,它们为牛棚里其他的牛的存在而愤怒。为了防止牛之间的互相打斗,Farmer John 想把这些牛安置在指定的隔间,所有牛中相邻两头的最近距离越大越好。那么,这个最大的最近距离是多少呢?

思路

  1. 确定最大值所在的区间,left 和 right
  2. 二分判断最小值为 mid 时数列是否可以安置 m 头奶牛
  3. 根据判断结果更新区间
  4. 得到符合题目要求的答案

代码

#include <bits/stdc++.h>
using namespace std;
int n, m, a[100005];
int mn, mx;
int Binary_answer(int l, int r)
{
    int left = l, right = r, mid, ans;
    while (left <= right)
    {
        mid = (left + right) / 2;
        // 计算最小值为mid时可以放置的奶牛数量
        int index = 1, cnt = 1; // index为当前坐标,cnt为奶牛数量
        for (int i = 2; i <= n; i++)
        {
            // 判断a[i]和当前坐标之前的差值
            if (a[i] - a[index] >= mid)
                index = i, cnt++; // 条件成立,更新坐标,奶牛数量+1
        }
        // 判断结果,更新区间
        if (cnt >= m)
            ans = mid, left = mid + 1;
        else
            right = mid - 1;
    }
    return ans;
}
int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
    }
    // 将坐标从小到大排序,方便计算
    sort(a + 1, a + n + 1);
    // 确定区间最大值和最小值
    mn = 1;           // 默认最小值为1,就是紧挨着
    mx = a[n] - a[1]; // 最大值为最后一个坐标减第一个坐标
    // 二分答案
    cout << Binary_answer(mn, mx);
    return 0;
}

  • 12
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值