目录
二分查找(模板题笔记)
整数二分算法
浮点数二分算法
二分答案
使用条件
二分查找又称折半查找,它是一种效率较高的查找方法。
二分搜索法,是通过不断缩小解可能存在的范围,从而求得问题最优解的方法。在程序设计竞赛中,经常可以见到二分搜索法和其他算法结合的题目。 --挑战程序设计竞赛
二分查找的时间复杂度为 O(logn),与线性查找的 O(n) 相比速度上得到了指数倍提高(x=log₂n,则 n=2^x )
二分 - OI Wiki
789. 数的范围 - AcWing
给定一个按照升序排列的长度为 n 的整数数组,以及 q 个查询。
对于每个查询,返回一个元素 k 的起始位置和终止位置(位置从 0 开始计数)。
如果数组中不存在该元素,则返回
-1 -1
。输入格式
第一行包含整数 n 和 q,表示数组长度和询问个数。
第二行包含 n 个整数(均在 1∼10000范围内),表示完整数组。
接下来 q 行,每行包含一个整数 k,表示一个询问元素。
输出格式
共 q 行,每行包含两个整数,表示所求元素的起始位置和终止位置。
如果数组中不存在该元素,则返回
-1 -1
。数据范围
1 ≤ n ≤ 100000
1 ≤ q ≤ 10000
1 ≤ k ≤ 10000输入样例:
6 3 1 2 2 3 3 4 3 4 5
输出样例:
3 4 5 5 -1 -1
解答
手敲
#include
using namespace std;
#define IOS ios_base::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
#define endl "\n"
#define ll long long
const ll N = 100005;
ll n, q, k, a[N];
signed main() {
cin >> n >> q;
for (int i = 0; i < n; i++)cin >> a[i];
while (q--) {
cin >> k;
int l = 0, r = n - 1;
while (l < r) {
int mid = l + r >> 1; //即(l + r)/2
if (a[mid] >= k)r = mid; //k <= a[mid],a[mid+x] a[mid]右边都为true,则缩小右边界(a[mid]可能为k,故无需+1)
else l = mid + 1; //a[mid-x],a[mid] < k a[mid]左边都为if(false),缩小左边界(a[mid]一定不为左边界,故+1)
}
if (a[l] != k) { cout << -1 << " " << -1 << endl; continue; }
else {
cout << l << " ";
l = 0, r = n - 1;
while (l < r) {
int mid = l + r + 1 >> 1; //因为是向下取整,有可能使left = mid而死循环,则+1避免
if (a[mid] <= k)l = mid; //a[mid-x],a[mid] <= k a[mid]以及左边都为true,缩小左边界(a[mid]可能为k,故无需+1)
else r = mid - 1; //k < a[mid],a[mid + x] a[mid]右边都为false 缩小右边界
}
cout << l << endl;
}
}
return 0;
}
使用upper_bound()和lower_bound(); (记得先sort)
C++ lower_bound()函数 -指向首个不小于 value
的元素的迭代器,若找不到返回 last
。(返回大于等于value的最小数的迭代器)
C++ upper_bound()函数 -指向首个大于 value
的元素的迭代器,若找不到返回 last
。(返回大于value的最小数的迭代器)
C++ equal_range()函数 //std::equal_range - C++中文 - API参考文档
C++ binary_search()函数
#include
using namespace std;
#define IOS ios_base::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
#define endl "\n"
#define ll long long
//const ll MAXN = 2 * (1e6 + 10);
const int MAX = 100005;
ll n, q, k, a[MAX];
signed main() {
IOS;
cin >> n >> q;
for (int i = 0; i < n; i++)cin >> a[i];
for (int i = 0; i < q; i++) {
cin >> k;
int index1 = lower_bound(a, a + n, k) - a;
int index2 = upper_bound(a, a + n, k) - a;
if (index1 != index2)cout << index1 << " " << index2 -1 << endl;
else cout << -1 << " " << -1 << endl;
}
return 0;
}
使用equal_range();
#include
using namespace std;
#define IOS ios_base::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
#define endl "\n"
#define ll long long
//const ll MAXN = 2 * (1e6 + 10);
const int MAX = 100005;
ll n, q, k, a[MAX];
signed main() {
IOS;
cin >> n >> q;
for (int i = 0; i < n; i++)cin >> a[i];
for (int i = 0; i < q; i++) {
cin >> k;
pair b = equal_range(a, a + n, k);
if (b.first - a != b.second - a)cout << b.first - a << " " << b.second - a - 1 << endl;
else cout << -1 << " " << -1 << endl;
}
return 0;
}
模板
bool check(int x) {/* ... */} // 检查x是否满足某种性质
// 可行解在右侧,满足条件时缩小右区间
int bsearch_1(int l, int r){
while (l < r){
int mid = l + r >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
return l;
}
// 可行解在左侧,满足条件时缩小左区间
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;
}
来源:https://www.acwing.com/blog/content/277/
790. 数的三次方根 - AcWing
给定一个浮点数 n,求它的三次方根。
输入格式
共一行,包含一个浮点数 n。
输出格式
共一行,包含一个浮点数,表示问题的解。
注意,结果保留 6 位小数。
数据范围
−10000 ≤ n ≤ 10000
输入样例:
1000.00
输出样例:
10.000000
解答
#include
using namespace std;
#define IOS ios_base::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
#define endl "\n"
#define ll long long
//const ll MAXN = 2 * (1e6 + 10);
const int MAX = 100005;
double n, mid, l=-10000, r=10000,eps = 1e-8;
signed main() {
IOS;
cin >> n;
while (r - l > eps) {
mid = (l + r) / 2;
//浮点除法不会取整,所以mid,l,r,都不用加1或减1
if (mid * mid * mid < n)l = mid; //如果mid < ³√n 中点在根左边,缩小左边界
else r = mid; //否则³√n <= mid 中点在根右边,缩小右边界
}
printf("%.6lf", l);
return 0;
}
模板
bool check(double 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;
}
来源:https://www.acwing.com/blog/content/277/
如果答案具有单调性且有范围,那么就可以二分枚举答案,在答案合法的条件下不断逼近最优(如最大值最小或最小值最大),最后一个合法的答案就是最优解,这便是二分答案 -二分答案入门 - Santiego - 博客园
即通过不断check()并且二分 从而逼近最优解
二分答案算法教程(超详细) - 掘金
1、答案在一个区间内(一般情况下,区间会很大,暴力超时)
2、直接搜索不好搜,但是容易判断一个答案可行不可行
3、该区间对题目具有单调性,即:在区间中的值越大或越小,题目中的某个量对应增加或减少。
此外,可能还会有一个典型的特征:求…最大值的最小 、 求…最小值的最大。
P1024 - 一元三次方程求解 - 洛谷
题目描述
有形如:ax^3 + bx^2 + cx + d = 0 这样的一个一元三次方程。给出该方程中各项的系数(a,b,c,d 均为实数),并约定该方程存在三个不同实根(根的范围在 -100 至 100 之间),且根与根之差的绝对值 ≥1。要求由小到大依次在同一行输出这三个实根(根与根之间留有空格),并精确到小数点后 2 位。
提示:记方程 f(x) = 0,若存在 2 个数 x1 和 x2,且 x1, f(x1)×f(x2)<0,则在 (x1,x2) 之间一定有一个根。
输入格式
一行,4 个实数 a, b, c, d。
输出格式
一行,3 个实根,从小到大输出,并精确到小数点后 2 位。
输入输出样例
输入 #1
1 -5 -4 20
输出 #1
-2.00 2.00 5.00
解答
#include
using namespace std;
double a, b, c, d;
double fx(double x) {
return a * x * x * x + b * x * x + c * x + d;
}
signed main() {
double l, r, mid, x1, x2;
scanf("%lf%lf%lf%lf", &a, &b, &c, &d);
for (int i = -100; i < 100; i++) {
l = i;
r = i + 1;
x1 = fx(l);
x2 = fx(r);
if (!x1) {
printf("%.2lf ", l);
}
if (x1 * x2 < 0) { //区间内有根。
while (r - l >= 0.001) {
mid = (l + r) / 2;
if (fx(mid) * fx(r) <= 0)
l = mid;
else
r = mid;
}
printf("%.2lf ", r);//输出右端点。
}
}
return 0;
}
P1873 - EKO / 砍树 - 洛谷
题目描述
伐木工人 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 米木材。
输入格式
第 1 行 2 个整数 N 和 M,N 表示树木的数量,M 表示需要的木材总长度。第 2 行 N 个整数表示每棵树的高度。
输出格式
1个整数,表示锯片的最高高度。
输入输出样例
输入 #1
4 7 20 15 10 17
输出 #1
15
输入 #2
5 20 4 42 40 26 46
输出 #2
36
解答
//把可能取值(1-longest)不断询问中点是否满足,不断递归,最终会得到一个满足值的边界(即结果)
#include
using namespace std;
typedef long long ll;
ll N, M, leftt, longest, trees[1000008];
bool check(int mid) {
ll sum = 0;
for (int i = 1; i <= N; i++)
if (trees[i] > mid)
sum += trees[i] - mid; //高的部分累加
return sum < M;
}
int main() {
scanf("%lld%lld", &N, &M);
for (int i = 1; i <= N; i++) {
scanf("%lld", &trees[i]);
longest = max(longest, trees[i]);//找到最长木材
}
while (leftt < longest) {
int mid = leftt + longest >> 1;
if (check(mid)) longest = mid;
else leftt = mid + 1;
}
cout << leftt - 1;
return 0;
}
还不懂可以看这个题解 剪绳子-AcWing