算法小白的努力之路———洛谷刷题打卡 ———— 二分查找与二分答案
前言 这里用的模板是参考了五点七边所传授的二分法,可以参考这个视频
二分查找为什么总是写错?
这里写一个二分模板,并附上边界条件的判断方法
整数二分模板
int binarySearch()
{
int l = -1, r = n;
while(l + 1 < r)
{
int mid = (l + r) >> 1; //右移一位相当于除以二
if(confition)
{
l = mid;
}
else
{
r = mid;
}
}
return l; // 看情况返回 l 或 r ,有时可以返回其他的,还是看题目
}
这里是边界条件的判断,IsBlue
就是模板中的 confition
小数二分模板,唯一的不同在 while
的 confition
判断上
int binarySearch()
{
int l = -1, r = n;
while(r - l > 0.001) //不一定是0.001, >号后面更题目所需的精度
{
int mid = (l + r) >> 1; //右移一位相当于除以二
if(confition)
{
l = mid;
}
else
{
r = mid;
}
}
return l; // 不存在取左边还是右边的问题,随意返回一边就可以
}
另外小数不存在取左边还是右边的问题,随意返回一边就可以
正式开始打卡 ↓↓↓
1. P2249 【深基13.例1】查找
题目描述
输入 n n n 个不超过 1 0 9 10^9 109 的单调不减的(就是后面的数字不小于前面的数字)非负整数 a 1 , a 2 , … , a n a_1,a_2,\dots,a_{n} a1,a2,…,an,然后进行 m m m 次询问。对于每次询问,给出一个整数 q q q,要求输出这个数字在序列中第一次出现的编号,如果没有找到的话输出 − 1 -1 −1 。
输入格式
第一行 2 2 2 个整数 n n n 和 m m m,表示数字个数和询问次数。
第二行 n n n 个整数,表示这些待查询的数字。
第三行 m m m 个整数,表示询问这些数字的编号,从 1 1 1 开始编号。
输出格式
输出一行, m m m 个整数,以空格隔开,表示答案。
样例 #1
样例输入 #1
11 3
1 3 3 3 5 7 9 11 13 15 15
1 3 6
样例输出 #1
1 2 -1
提示
数据保证, 1 ≤ n ≤ 1 0 6 1 \leq n \leq 10^6 1≤n≤106, 0 ≤ a i , q ≤ 1 0 9 0 \leq a_i,q \leq 10^9 0≤ai,q≤109, 1 ≤ m ≤ 1 0 5 1 \leq m \leq 10^5 1≤m≤105
本题输入输出量较大,请使用较快的 IO 方式。
--------------------------------------------------------------------
题解 ↑↑↑
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1000010;
int n, m;
int q[N];
int binarySearch(int x)
{
int l = 0, r = n + 1;
while (l + 1 < r)
{
int mid = (l + r) >> 1;
if (q[mid] < x)
{
l = mid;
}
else
{
r = mid;
}
}
if (q[r] == x) return r; // 找到了目标元素
else return -1; // 没有找到目标元素
}
int main()
{
scanf("%d%d", &n, &m); // 输入数字个数和查询次数
for (int i = 1; i <= n; i++)
{
scanf("%d", &q[i]); // 输入数字序列
}
while (m--)
{
int x;
scanf("%d", &x); // 输入查询数字
printf("%d ", binarySearch(x)); // 输出查询结果
}
return 0;
}
--------------------------------------------------------------------
2. P1102 A-B 数对
题目背景
出题是一件痛苦的事情!
相同的题目看多了也会有审美疲劳,于是我舍弃了大家所熟悉的 A+B Problem,改用 A-B 了哈哈!
题目描述
给出一串正整数数列以及一个正整数 C C C,要求计算出所有满足 A − B = C A - B = C A−B=C 的数对的个数(不同位置的数字一样的数对算不同的数对)。
输入格式
输入共两行。
第一行,两个正整数 N , C N,C N,C。
第二行, N N N 个正整数,作为要求处理的那串数。
输出格式
一行,表示该串正整数中包含的满足 A − B = C A - B = C A−B=C 的数对的个数。
样例 #1
样例输入 #1
4 1
1 1 2 3
样例输出 #1
3
提示
对于 75 % 75\% 75% 的数据, 1 ≤ N ≤ 2000 1 \leq N \leq 2000 1≤N≤2000。
对于 100 % 100\% 100% 的数据, 1 ≤ N ≤ 2 × 1 0 5 1 \leq N \leq 2 \times 10^5 1≤N≤2×105, 0 ≤ a i < 2 30 0 \leq a_i <2^{30} 0≤ai<230, 1 ≤ C < 2 30 1 \leq C < 2^{30} 1≤C<230。
2017/4/29 新添数据两组
--------------------------------------------------------------------
题解 ↑↑↑
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long LL; // 定义数据类型为 long long,用于存储结果
const int N = 200010;
int n, c;
int q[N];
LL res;
void binarySearch()
{
for (int i = 0; i < n; i++)
{
int l = -1, r = n;
while (l + 1 < r)
{
int mid = (l + r) >> 1; // 二分查找的中间位置
if (q[mid] < q[i] + c) // 如果 q[mid] 小于 q[i] + c,则说明满足条件的数对在右侧
{
l = mid;
}
else
{
r = mid;
}
}
LL res1 = r; // 记录右侧边界的位置
l = -1, r = n;
while (l + 1 < r)
{
int mid = l + r >> 1; // 二分查找的中间位置
if (q[mid] <= q[i] + c) // 如果 q[mid] 小于等于 q[i] + c,则说明满足条件的数对在右侧
{
l = mid;
}
else
{
r = mid;
}
}
LL res2 = l; // 记录左侧边界的位置
res += (res2 - res1 + 1); // 将满足条件的数对个数累加到结果中
}
}
int main()
{
cin >> n >> c; // 输入数组长度和 c 的值
for (int i = 0; i < n; i++)
{
cin >> q[i]; // 输入数组元素
}
sort(q, q + n); // 对数组进行排序
binarySearch(); // 进行二分查找
cout << res << endl; // 输出结果
return 0;
}
温馨提示:必须用 long long 储存答案 ,因为有一个数据答案是10000000000,不用只能得92分。
--------------------------------------------------------------------
3. P1873 [COCI2011-2012#5] EKO / 砍树
题目描述
伐木工人 Mirko 需要砍 M M M 米长的木材。对 Mirko 来说这是很简单的工作,因为他有一个漂亮的新伐木机,可以如野火一般砍伐森林。不过,Mirko 只被允许砍伐一排树。
Mirko 的伐木机工作流程如下:Mirko 设置一个高度参数 H H H(米),伐木机升起一个巨大的锯片到高度 H H H,并锯掉所有树比 H H H 高的部分(当然,树木不高于 H H H 米的部分保持不变)。Mirko 就得到树木被锯下的部分。例如,如果一排树的高度分别为 20 , 15 , 10 20,15,10 20,15,10 和 17 17 17,Mirko 把锯片升到 15 15 15 米的高度,切割后树木剩下的高度将是 15 , 15 , 10 15,15,10 15,15,10 和 15 15 15,而 Mirko 将从第 1 1 1 棵树得到 5 5 5 米,从第 4 4 4 棵树得到 2 2 2 米,共得到 7 7 7 米木材。
Mirko 非常关注生态保护,所以他不会砍掉过多的木材。这也是他尽可能高地设定伐木机锯片的原因。请帮助 Mirko 找到伐木机锯片的最大的整数高度 H H H,使得他能得到的木材至少为 M M M 米。换句话说,如果再升高 1 1 1 米,他将得不到 M M M 米木材。
输入格式
第 1 1 1 行 2 2 2 个整数 N N N 和 M M M, N N N 表示树木的数量, M M M 表示需要的木材总长度。
第 2 2 2 行 N N N 个整数表示每棵树的高度。
输出格式
1 1 1 个整数,表示锯片的最高高度。
样例 #1
样例输入 #1
4 7
20 15 10 17
样例输出 #1
15
样例 #2
样例输入 #2
5 20
4 42 40 26 46
样例输出 #2
36
提示
对于 100 % 100\% 100% 的测试数据, 1 ≤ N ≤ 1 0 6 1\le N\le10^6 1≤N≤106, 1 ≤ M ≤ 2 × 1 0 9 1\le M\le2\times10^9 1≤M≤2×109,树的高度 < 1 0 9 <10^9 <109,所有树的高度总和 > M >M >M。
--------------------------------------------------------------------
题解 ↑↑↑
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long LL;
const int N = 1000010;
int n, m;
int q[N];
// 找到数组中的最大值的索引
int tmax(int q[])
{
int tmax = 0;
for(int i = 0; i < n; i ++)
{
if(q[i] > q[tmax]) tmax = i;
}
return q[tmax];
}
// 二分查找
LL binarySearch()
{
int l = 0, r = tmax(q);
while(l + 1 < r)
{
int mid = (l + r) >> 1;
LL res1 = 0;
// 遍历每棵树,计算总木材长度
for(int i = 0; i < n; i ++)
if(q[i] - mid >= 0) res1 += q[i] - mid;
if(res1 < m)
{
r = mid;
}
else
{
l = mid;
}
}
return l;
}
int main()
{
cin >> n >> m;
for(int i = 0; i < n; i ++)
{
cin >> q[i];
}
sort(q, q + n);
LL res = binarySearch();
cout << res << endl;
return 0;
}
这段代码使用了二分查找来寻找最大高度。首先,通过tmax
函数找到树木中的最大高度,将其作为初始的右边界。然后,进入二分查找循环,直到左边界大于右边界。在每次循环中,计算当前中间高度对应的总木材长度,如果总木材长度小于需要的木材总长度,则将右边界更新为当前中间高度;否则,将左边界更新为当前中间高度。最后,输出左边界作为锯片的最大高度。
请注意,这段代码假设输入的树木高度已经按照升序排序。如果输入时未排序的,需要先对树木高度进行排序。
--------------------------------------------------------------------
4. P1024 [NOIP2001 提高组] 一元三次方程求解
题目描述
有形如: a x 3 + b x 2 + c x + d = 0 a x^3 + b x^2 + c x + d = 0 ax3+bx2+cx+d=0 这样的一个一元三次方程。给出该方程中各项的系数( a , b , c , d a,b,c,d a,b,c,d 均为实数),并约定该方程存在三个不同实根(根的范围在 − 100 -100 −100 至 100 100 100 之间),且根与根之差的绝对值 ≥ 1 \ge 1 ≥1。要求由小到大依次在同一行输出这三个实根(根与根之间留有空格),并精确到小数点后 2 2 2 位。
提示:记方程 f ( x ) = 0 f(x) = 0 f(x)=0,若存在 2 2 2 个数 x 1 x_1 x1 和 x 2 x_2 x2,且 x 1 < x 2 x_1 < x_2 x1<x2, f ( x 1 ) × f ( x 2 ) < 0 f(x_1) \times f(x_2) < 0 f(x1)×f(x2)<0,则在 ( x 1 , x 2 ) (x_1, x_2) (x1,x2) 之间一定有一个根。
输入格式
一行, 4 4 4 个实数 a , b , c , d a, b, c, d a,b,c,d。
输出格式
一行, 3 3 3 个实根,从小到大输出,并精确到小数点后 2 2 2 位。
样例 #1
样例输入 #1
1 -5 -4 20
样例输出 #1
-2.00 2.00 5.00
提示
【题目来源】
NOIP 2001 提高组第一题
--------------------------------------------------------------------
题解 ↑↑↑
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
double a, b, c, d; // 输入的系数
double x1, x2; // 函数值
int num; // 计数器
double f(double x)
{
return a * x * x * x + b * x * x + c * x + d; // 定义函数 f(x)
}
void binarySearch()
{
for (double i = -100; i <= 100; i++)
{
double l = i, r = i + 1; // 区间左右边界
x1 = f(l); // 计算左边界的函数值
x2 = f(r); // 计算右边界的函数值
if (!x1)
{
printf("%.2lf ", l); // 如果左边界的函数值为零,则输出左边界作为根
num++;
}
if (x1 * x2 < 0)
{
while (r - l >= 0.001) // 当区间长度大于等于 0.001 时进行迭代
{
double mid = (l + r) / 2; // 取区间的中点
if (f(mid) * f(r) < 0) // 如果中点和右边界的函数值异号
{
l = mid; // 更新左边界为中点
}
else
{
r = mid; // 更新右边界为中点
}
}
printf("%.2lf ", r); // 输出找到的根
num++;
}
if (num == 3) // 如果找到了三个根,则退出循环
{
break;
}
}
}
int main()
{
cin >> a >> b >> c >> d; // 输入系数
binarySearch(); // 进行二分查找
return 0;
}
这段代码通过二分查找的方式寻找给定函数的根。它遍历了区间 [-100, 100],计算左右边界的函数值,并根据函数值的符号进行判断和迭代,直到找到满足条件的根或者找到了三个根为止。最后,输出找到的根。
--------------------------------------------------------------------
5. P1678 烦恼的高考志愿
题目背景
计算机竞赛小组的神牛 V 神终于结束了高考,然而作为班长的他还不能闲下来,班主任老 t 给了他一个艰巨的任务:帮同学找出最合理的大学填报方案。可是 v 神太忙了,身后还有一群小姑娘等着和他约会,于是他想到了同为计算机竞赛小组的你,请你帮他完成这个艰巨的任务。
题目描述
现有 m m m 所学校,每所学校预计分数线是 a i a_i ai。有 n n n 位学生,估分分别为 b i b_i bi。
根据 n n n 位学生的估分情况,分别给每位学生推荐一所学校,要求学校的预计分数线和学生的估分相差最小(可高可低,毕竟是估分嘛),这个最小值为不满意度。求所有学生不满意度和的最小值。
输入格式
第一行读入两个整数 m , n m,n m,n。 m m m 表示学校数, n n n 表示学生数。
第二行共有 m m m 个数,表示 m m m 个学校的预计录取分数。第三行有 n n n 个数,表示 n n n 个学生的估分成绩。
输出格式
输出一行,为最小的不满度之和。
样例 #1
样例输入 #1
4 3
513 598 567 689
500 600 550
样例输出 #1
32
提示
数据范围:
对于 30 % 30\% 30% 的数据, 1 ≤ n , m ≤ 1000 1\leq n,m\leq1000 1≤n,m≤1000,估分和录取线 ≤ 10000 \leq10000 ≤10000;
对于 100 % 100\% 100% 的数据, 1 ≤ n , m ≤ 100000 1\leq n,m\leq100000 1≤n,m≤100000,估分和录取线 ≤ 1000000 \leq 1000000 ≤1000000 且均为非负整数。
--------------------------------------------------------------------
题解 ↑↑↑
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long LL;
const int N = 100010;
int m, n;
int a[N], b[N];
LL res = 0;
LL binarySearch()
{
for (int i = 0; i < n; i++)
{
int l = -1, r = m;
while (l + 1 < r)
{
int mid = (l + r) >> 1; // 二分查找的中间位置
if (a[mid] <= b[i])
{
l = mid; // 如果中间位置的值小于等于目标值,则更新左边界
}
else
{
r = mid; // 否则,更新右边界
}
}
if (b[i] <= a[0])
{
res += a[0] - b[i]; // 如果目标值小于等于数组a的最小值,则累加差值到结果中
}
else
{
res += min(abs(a[l] - b[i]), abs(a[r] - b[i])); // 否则,计算与两个边界值的差的绝对值,并取最小值累加到结果中
}
}
return res;
}
int main()
{
cin >> m >> n;
// 输入数组a的元素
for (int i = 0; i < m; i++)
cin >> a[i];
sort(a, a + m); // 对数组a进行排序
// 输入数组b的元素
for (int i = 0; i < n; i++)
cin >> b[i];
cout << binarySearch() << endl; // 输出结果
return 0;
}
--------------------------------------------------------------------
6. P2440 木材加工
题目背景
要保护环境
题目描述
木材厂有 n n n 根原木,现在想把这些木头切割成 k k k 段长度均为 l l l 的小段木头(木头有可能有剩余)。
当然,我们希望得到的小段木头越长越好,请求出 l l l 的最大值。
木头长度的单位是 cm \text{cm} cm,原木的长度都是正整数,我们要求切割得到的小段木头的长度也是正整数。
例如有两根原木长度分别为 11 11 11 和 21 21 21,要求切割成等长的 6 6 6 段,很明显能切割出来的小段木头长度最长为 5 5 5。
输入格式
第一行是两个正整数 n , k n,k n,k,分别表示原木的数量,需要得到的小段的数量。
接下来 n n n 行,每行一个正整数 L i L_i Li,表示一根原木的长度。
输出格式
仅一行,即 l l l 的最大值。
如果连
1cm
\text{1cm}
1cm 长的小段都切不出来,输出 0
。
样例 #1
样例输入 #1
3 7
232
124
456
样例输出 #1
114
提示
数据规模与约定
对于 100 % 100\% 100% 的数据,有 1 ≤ n ≤ 1 0 5 1\le n\le 10^5 1≤n≤105, 1 ≤ k ≤ 1 0 8 1\le k\le 10^8 1≤k≤108, 1 ≤ L i ≤ 1 0 8 ( i ∈ [ 1 , n ] ) 1\le L_i\le 10^8(i\in[1,n]) 1≤Li≤108(i∈[1,n])。
--------------------------------------------------------------------
题解 ↑↑↑
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 100010;
LL n, k;
LL nl[N];
LL ans;
LL binarySearch()
{
int l = 0, r = nl[n - 1] + 1;
while (l + 1 < r)
{
LL sum = 0;
int mid = (l + r) >> 1; // 二分查找的中间位置
for (int i = 0; i < n; i++)
{
sum += nl[i] / mid; // 计算每个元素除以中间位置的商,并累加到sum中
}
if (sum >= k)
{
l = mid; // 如果sum大于等于k,说明中间位置的值偏小,更新左边界
}
else
{
r = mid; // 否则,中间位置的值偏大,更新右边界
}
}
return l;
}
int main()
{
cin >> n >> k;
// 输入数组nl的元素
for (int i = 0; i < n; i++)
{
cin >> nl[i];
}
sort(nl, nl + n); // 对数组nl进行排序
cout << binarySearch() << endl; // 输出结果
return 0;
}