• 二分法简介


    定义

    对于区间[a,b]上连续不断且f(a)·f(b)<0的函数y=f(x),通过不断地把函数f(x)的零点所在的区间一分为二,使区间的两个端点逐步逼近零点,进而得到零点近似值的方法叫二分法。

    例如对于一个连续的范围[0, 10],寻找x2+3x+4=22的一个解, 由于在取值范围内函数是随x单调增的,对于一个单调上升的x序列[0,10],我们要寻找的是最后一个值x使得x2+3x+4<22。一个显然的方法是,x = 1,1.000001,1.000002…,但这样费时费力,不好寻找,不妨使用二分法解决问题。(参见北师大版初三教材(O-O))

    流程图

    对于一个二分法,通常有两个要素:

    1. l,r的界
    2. check(x)函数,判断x是否还可行

    通常利用下面的方法:

    while (l <= r) {
        mid = (l+r)/2;
        if (check(mid)) l = mid + 1;
        r = mid - 1;
    }

    二分法代码很简单,主要是思路和check函数。下面用例题分析一下

    例题

    跳石头

    描述

    一年一度的“跳石头”比赛又要开始了!
    这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石。组委会已经选择好了两块岩石作为比赛起点和终点。在起点和终点之间,有 N 块岩石(不含起点和终 点的岩石)。在比赛过程中,选手们将从起点出发,每一步跳向相邻的岩石,直至到达终点。
    为了提高比赛难度,组委会计划移走一些岩石,使得选手们在比赛过程中的最短跳 跃距离尽可能长。由于预算限制,组委会至多从起点和终点之间移走 M 块岩石(不能 移走起点和终点的岩石)。

    格式

    输入格式

    输入第一行包含三个整数 L,N,M,分别表示起点到终点的距离,起点和终 点之间的岩石数,以及组委会至多移走的岩石数。接下来 N 行,每行一个整数,第 i 行的整数Di(0<Di<L)表示第 i 块岩石与 起点的距离。这些岩石按与起点距离从小到大的顺序给出,且不会有两个岩石出现在同一个位置。

    输出格式

    输出只包含一个整数,即最短跳跃距离的最大值。

    样例1

    • 样例输入1

      25 5 2
      2
      11
      14
      17
      21

    • 样例输出1

      4

    限制

    对于20%的数据,0MN10
    对于50%的数据,0MN100
    对于100%的数据,0MN500001L1000000000

    提示

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

    分析

    当时看到这道题很脑大,用贪心+堆维护,只搞到20分。其实二分思路很简单,因为答案是在0..L之间的,只需要找到最后一个l使得数据符合条件。因此可以二分答案,最后得出结果。

    #include <iostream>
    #include <cstdio>
    using namespace std;
    
    int a[50005];
    int L, M, N;
    
    int check(int x) {
        int num = 0, last = 0;
        for (int i = 1; i <= N; i++) {
            if (a[i]-last < x)
                num++;
            else
                last = a[i];
        }
        return num<=M?1:0;
    }
    
    int main() {
        scanf("%d%d%d", &L, &N, &M);
        for (int i = 1; i <= N; i++) scanf("%d", &a[i]);
        a[++N] = L;
        int l = 0, r = L;
        while (l < r) {
            int mid = (l+r)>>1;
            if (check(mid)) l = mid+1;
            else r = mid-1;
        }
        if (!check(l)) l--;
        printf("%d
    ", l);
        return 0;
    }

    借教室

    描述

    在大学期间,经常需要租借教室。大到院系举办活动,小到学习小组自习讨论,都需要向学校申请借教室。教室的大小功能不同,借教室人的身份不同,借教室的手续也不一样。
    面对海量租借教室的信息,我们自然希望编程解决这个问题。我们需要处理接下来n天的借教室信息,其中第i天学校有ri个教室可供租借。共有m份订单,每份订单用三个正整数描述,分别为dj,sj,tj,表示某租借者需要从第sj天到第tj天租借教室(包括第sj天和第tj天),每天需要租借dj个教室。
    我们假定,租借者对教室的大小、地点没有要求。即对于每份订单,我们只需要每天提供dj个教室,而它们具体是哪些教室,每天是否是相同的教室则不用考虑。
    借教室的原则是先到先得,也就是说我们要按照订单的先后顺序依次为每份订单分配教室。如果在分配的过程中遇到一份订单无法完全满足,则需要停止教室的分配,通知当前申请人修改订单。这里的无法满足指从第sj天到第tj天中有至少一天剩余的教室数量不足dj个。
    现在我们需要知道,是否会有订单无法完全满足。如果有,需要通知哪一个申请人修改订单。

    格式

    输入格式

    第一行包含两个正整数n,m,表示天数和订单的数量。
    第二行包含n个正整数,其中第i个数为ri,表示第i天可用于租借的教室数量。
    接下来有m行,每行包含三个正整数dj,sj,tj,表示租借的数量,租借开始、结束分别在第几天。
    每行相邻的两个数之间均用一个空格隔开。天数与订单均用从1开始的整数编号。

    输出格式

    如果所有订单均可满足,则输出只有一行,包含一个整数0。否则(订单无法完全满足)输出两行,第一行输出一个负整数-1,第二行输出需要修改订单的申请人编号。

    样例1

    • 样例输入1[复制]

      4 3
      2 5 4 3
      2 1 3
      3 2 4
      4 2 4

    • 样例输出1[复制]

      -1
      2

    限制

    每个测试点1s
    对于10%的数据,有1≤ n,m≤ 10;
    对于30%的数据,有1≤ n,m≤1000;
    对于70%的数据,有1≤ n,m≤ 10^5;
    对于100%的数据,有1≤n,m≤10^6,0≤ri,dj≤10^9,1≤sj≤tj≤n。

    分析

    第一反应是线段树,不过显然这数据线段树会被卡常数。考虑二分法。关键是check函数,把前l个请求用类似前缀和的方法叠加起来,如果需求总数超过房间数,就返回错误。

    主要方法是:对于一个需求si(开始),ti(结束),di(数量),在si时需求加上di,ti时减去di,然后前缀累加,一旦大于总教室数就报false。

    bool judge(int v) {
        int sum = 0;
        memset(temp, 0, sizeof temp);
        for (int i = 1; i <= v; i++) {
            temp[sj[i]] += dj[i];
            temp[tj[i]+1] -= dj[i];
        }
        for (int i = 1; i <= n; i++) {
            sum += temp[i];
            if (sum > a[i])
                return false;
        }
        return true;
    }

    剩余过程脑补(O-O)

  • 相关阅读:
    课堂讨论电子版
    轻量级推送及在此基础上的即时通讯探索(1)
    第十章 Android的消息机制
    第十一章 Android的线程和线程池
    第八章 理解Window和WindowMannager
    第四章 View的工作原理
    第三章 View的事件体系
    第二章 IPC
    Android独立音量播放器
    SpringMVC笔记
  • 原文地址:https://www.cnblogs.com/ljt12138/p/6684382.html
Copyright © 2020-2023  润新知