lower_bound 和 二分
老是写不好二分,特此学习总结一下。
lower_bound 和 upper_bound
这两个函数基本上能替代手写的二分查找,但是千万别弄错俩函数的作用。
lower_bound 返回数组中第一个不小于目标值的元素的iterator,(第一个>=target的数)
upper_bound 返回数组中第一个大于目标值的元素的iterator, (第一个>target的数)
手写二分查找
主要是熟练使用二分,不单单只是用来查找一个数 ,而是能指哪打哪,用来解决带有单调性的数组问题,这才是二分的威力。
经典二分模板, 一个是区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用, 一个是区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用。当数组为前段false,后端true时候,用(1),前段true,后段false时,用(2)
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;
}
// 作者:yxc
// 链接:https://www.acwing.com/blog/content/277/
// 来源:AcWing
// 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
二分查找 :数的范围
经典题AcWing 789. 数的范围,给定数组,如1 2 2 3 3 4, 和target, 如3,找到3的范围[3,4]。
可见,本题需要找到arr[low] <= target的下界,还要找到arr[up] >= target的下界,根据区间是前false后true还是前true后false来使用二分方法。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
vector<int> arr {};
int n, m, target;
// 两种方法用来找符合checker的最左边以及符合checker的最右边。
int low(int l, int r, int target) {
while(l<r) {
int mid = l+r>> 1;
if (arr[mid]>=target) r = mid;
else l = mid+1;
}
return l;
}
int up(int l, int r, int target) {
while (l<r) {
int mid = l+r+1 >> 1;
if (arr[mid]<=target) l = mid;
else r = mid-1;
}
return r;
}
int main() {
cin >> n >> m;
arr.resize(n);
for (int i=0; i<n; i++) cin >> arr[i];
for (int i=0; i<m; i++) {
cin >> target;
int lower = low(0, n-1, target);
int upper = up(lower, n-1, target);
if (arr[lower]!=target) cout << "-1 -1" << endl;
else
cout << lower<< " " << upper << endl;
}
}
二分巧用-最大上升子序列
数组坐标i代表长度为i+1的子序列。长度为i+1的子序列,最后的数为arr[i]。如果如果遇到arr.back() < val, 则有更大子序列,arr.push_back(val)。反之对前面的第一个大于等于val的元素进行更新。
arr : 1 3 4
进入2,则最优长度为2的数列的结尾数为2 -> 即 1 2 4
#include <vector>
#include <cstdio>
using namespace std;
int n, val, len;
vector <int> stk;
int myLowerBound(int l, int r, const vector<int> &stk, int val) {
while (l<r) {
int mid = (l+r) / 2;
if (stk[mid] >= val ) r = mid;
else l = mid+1;
}
return l;
}
int main() {
scanf("%d", &n);
stk.reserve(n);
scanf("%d", &val);
stk.push_back(val);
for (int i=1; i<n; i++) {
scanf("%d", &val);
if (stk.back() >= val) { // 可以跳过 == val
int idx = myLowerBound(0, stk.size(), stk, val);
stk[idx] = val; // stk[0] = val, if stk[0] > val, otherwise, val ~ [stk[i], stk[j]] 和 (stk.back(), +)
}
else stk.push_back(val);
}
printf("%d", stk.size());
}
浮点二分
模板, 主要就是加入了eps判断是否r和l相等
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;
}
例子:求三次方根
设置精度 -> cout.setf(ios::fixed); cout << setprecision(6) << f << endl; // iomanip
注意,出现精度问题要换double, 如果是用cstdio,要用%lf
#include <cstdio>
#include <cmath>
#include <iostream>
// #include <iomanip>
// cout << setiosflags(ios::fixed) <<setprecision(6) << l;
// round会四舍五入,printf也会四舍五入]
// scan double -> %lf
using namespace std;
double n;
double l = -30., r = 30.;
double eps = 1e-7;
int main() {
scanf("%lf", &n);
while (r-l > eps) {
double mid = (l+r) / 2;
if (mid*mid*mid >= n) r = mid;
else l = mid;
}
printf("%.6lf", l);
}