二分法简介
定义
对于区间[a,b]上连续不断且f(a)·f(b)<0的函数y=f(x),通过不断地把函数f(x)的零点所在的区间一分为二,使区间的两个端点逐步逼近零点,进而得到零点近似值的方法叫二分法。
例如对于一个连续的范围[0, 10],寻找
流程图
对于一个二分法,通常有两个要素:
- l,r的界
- 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 行的整数
输出格式
输出只包含一个整数,即最短跳跃距离的最大值。
样例1
样例输入1
25 5 2
2
11
14
17
21样例输出1
4
限制
对于20%的数据,
对于50%的数据,
对于100%的数据,
提示
对于样例。将与起点距离为 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\n", 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)