二分
二分
二分:用来查找某类性质的数。
注意事项:边界问题。
模板
整数二分
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;
}

浙公网安备 33010602011771号