浅谈二分答案

对于二分,我已经懵逼了好长时间了,今天自己补了补,总算是搞懂了。这次来简单讲解一下二分答案。

在一个单调区间里查找答案,在正常的思维下都是用暴力枚举。比如说有几个不同大小、已经从小到大排列好的球,
它们的直径分别为1,2,3,4,5,10,20(你一开始当然不会知道每一个球的直径),让你从中找出直径为10cm的那一个球。
一般人都会这样做:从头到尾一个一个的量,直到找到答案为止,这样复杂度最坏情况下为O(n),我们有没有更快的方法呢?
答案就是用二分,先从区间里找排在中间的元素,找到第四个元素为4,发现4比10小,所以答案应该在第四个元素的后面。然后再从第五位到最后一位的区间里找排在中间的元素,找到第六个元素10。OK,问题解决,只用了两次,复杂度为O(log n),优化了很多。

二分答案,就是用二分的方法,在可能的答案区间里找出问题的答案,大多数情况下用于求解满足某种条件下的最大(小)值,前提是答案具有单调性,同时也可以理解为是一种倒推方法(先找答案在判断答案是否可行、有没有更优解)。

模板就是二分,面对整数时,这个模板是万能的,只需要根据情况更改r=mid-1和 l=mid+1的位置即可;
(当然还有更好的,然而我太菜了并且懒,就只能打出这了。用这个模板试了几个题倒是都过了,要是有问题请及时指出)

    int l=1,r=ll;// 1 是答案的最小值,ll是答案的最大值
    while(l<=r)
    {
        int mid=(l+r)>>1,q=check(mid);//“>>1”相当于“/2”
        if(q>m)r=mid-1;
        else l=mid+1;
    }

(最好先看懂二分模板)

举个栗子吧

洛谷P1873砍树

题目描述

伐木工人米尔科需要砍倒M米长的木材。这是一个对米尔科来说很容易的工作,因为他有一个漂亮的新伐木机,可以像野火一样砍倒森林。不过,米尔科只被允许砍倒单行树木。

米尔科的伐木机工作过程如下:米尔科设置一个高度参数H(米),伐木机升起一个巨大的锯片到高度H,并锯掉所有的树比H高的部分(当然,树木不高于H米的部分保持不变)。米尔科就行到树木被锯下的部分。

例如,如果一行树的高度分别为20,15,10和17,米尔科把锯片升到15米的高度,切割后树木剩下的高度将是15,15,10和15,而米尔科将从第1棵树得到5米,从第4棵树得到2米,共得到7米木材。

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

输入输出格式

输入格式:
第1行:2个整数N和M,N表示树木的数量(1<=N<=1000000),M表示需要的木材总长度(1<=M<=2000000000)

第2行:N个整数表示每棵树的高度,值均不超过1000000000。所有木材长度之和大于M,因此必有解。

输出格式:
第1行:1个整数,表示砍树的最高高度。

输入输出样例

输入样例#1:
5 20
4 42 40 26 46
输出样例#1:
36

这个题的答案有明显的单调性,砍树的高度越低,得到的木材就越多,所以用二分在答案区间里找答案就行了。

#include<iostream>
#include<cstdio>
long long m,n,a[1000005],temp;//因为我菜,所以就全设成long long;
using namespace std;
long long check(long long x)//这个check函数是二分的最重要的一环
{
    long long ans=0;
    for(int i=1;i<=n;i++)
    {
        if(a[i]>x)
        ans=ans-x+a[i];//ans用来记录能够得到的木材长度
     } 
     return ans;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        temp=max(temp,a[i]);//temp用来记录最高的树的高度
    }
    long long l=1,r=temp;//把右边界设成最高的树的高度
    while(l<=r)//二分操作
    {
        long long mid=(l+r)>>1,q=check(mid);
        if(q<m)r=mid-1;
        else l=mid+1;
    }
    printf("%d",r);
    return 0;
}

check函数是二分中最重要的一环,二分的模板可以背,但是check函数就因题而异了。砍树这个题的check函数还是比较好理解的,让我们再看一道题———–

洛谷P2678 跳石头

题目背景

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

题目描述

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

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

输入输出格式

输入格式:
第一行包含三个整数 L,N,M ,分别表示起点到终点的距离,起点和终点之间的岩石数,以及组委会至多移走的岩石数。保证L≥1 且 N≥M≥0 。

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

输出格式:
一个整数,即最短跳跃距离的最大值。

输入输出样例

输入样例#1: 复制
25 5 2
2
11
14
17
21
输出样例#1: 复制
4
说明

输入输出样例 1 说明:将与起点距离为 2 和 14 的两个岩石移走后,最短的跳跃距离为 4 (从与起点距离 17 的岩石跳到距离 21 的岩石,或者从距离 21 的岩石跳到终点)。

另:对于20% 的数据, 0≤M≤N≤10 。

对于 50% 的数据,0≤M≤N≤100 。

对于 100% 的数据, 0≤M≤N≤50,000,1≤L≤1,000,000,000 。

这个题是让你求最短跳跃距离的最大值,很明显,是一个二分答案题。

上代码

这里的check已经能看出来不一样了,这里的check实际上是一种贪心的策略,因为有一点是可以确定的:拿走的石头越多,最短跳跃距离越大(当然拿石头是有一定策略的)。因为有拿石头的限制,所以也要让在符合条件下拿走的石头最少。详情见代码;

#include<iostream>
#include<cstdio>
int ll,m,n,a[50005];
using namespace std;
int check(int x)
{
    int ans=0,t=0;//ans为拿走的石头数量,t为上一块石头的位置
    for(int i=1;i<=n;i++)
    {
        while(a[i]-t<x&&i<=n)//如果两块石头的间距小于最小距离,就把这块石头移走,更新后面一块石头的位置
        ans++,i++;

        t=a[i];//更新前一块石头的位置
     } 
     return ans;
}
int main()
{
    scanf("%d%d%d",&ll,&n,&m);
    for(int i=1;i<=n;i++)
    scanf("%d",&a[i]);
    a[++n]=ll;//记录终点位置
    int l=1,r=ll;
    while(l<=r)
    {
        int mid=(l+r)>>1,q=check(mid);
        if(q>m)r=mid-1;//如果以当前的mid为最小跳跃距离时,要拿走的石头比限定的要多,
                       //说明mid的值大了,就要更新右边界,继续查找
        else l=mid+1;//同理。
//但是有一种特殊情况,当q==m 时,也要进行这步操作,就是看看在拿走同样多的石头时,有没有更大的最短跳跃距离
    }
    printf("%d",r);
    return 0;
}//完美结束;

血小板真是敲可爱QwQ

这里写图片描述

发布了5 篇原创文章 · 获赞 9 · 访问量 6438
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 技术黑板 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览