优雅的二分可以使复杂度达到O(n log n)
二分查找——最初的模样
首先保证的是,二分查找的序列是升序的。
查找小于等于temp的第一个元素位置
void binary_lower(int temp){ //小于等于temp的第一个元素
int l = 0, r = n - 1;
while(l <= r){
int middle = (l + r) >> 1;
if(a[middle] >= temp){
r = middle - 1;
}else{
l = middle + 1;
}
}
printf("%d\n", l);
}
查找大于temp的第一个元素位置
void binary_upper(int temp){ //大于temp的第一个元素
int l = 0, r = n - 1;
while(l <= r){
int middle = (l + r) >> 1;
if(a[middle] > temp){
r = middle - 1;
}else{
l = middle + 1;
}
}
printf("%d\n", l);
}
- 不难发现,两段代码的差别仅在一个运算符符号,看看查找的结果吧
int main(){
scanf("%d", &n);
for(int i = 0; i < n; ++i){
scanf("%d", &a[i]);
}
while(1){
int t;
scanf("%d", &t);
binary_lower(t);
//printf("%d\n", lower_bound(a, a+n, t) - a);
binary_upper(t);
//printf("%d\n", upper_bound(a, a+n, t) - a);
}
return 0;
}
如输入样例:
6
1 2 2 3 3 4
3
输出:
3
5
lower_bound()和upper_bound()
- 正如上面代码注释的部分:
- C++提供的
lower_bound()
即查找小于等于temp的第一个元素位置,upper_bound()
即查找大于temp的第一个元素位置。
·【AcWing】·数的范围
题目链接:·【acwing】·数的范围
- 查找temp的起始位置(即小于等于temp的第一个元素位置()
- 查找temp的终止位置(即大于temp的第一个元素位置 - 1)
- 【注】:temp在序列中还必须存在,若不存在,需要额外处理一下
于是,实现代码只需要在原来的binary_lower()
和 binary_upper()
稍作改动
————————————完整代码————————————
#include <cstdio>
#include <algorithm>
using namespace std;
int n;
int a[100005];
int binary_lower(int temp){
int l = 0, r = n - 1;
bool flag = false;
while(l <= r){
int middle = (l + r) >> 1;
if(a[middle] >= temp){
if(a[middle] == temp){ //特别判断
flag = true;
}
r = middle - 1;
}else{
l = middle + 1;
}
}
if(!flag){ //不存在,返回-1
return -1;
}else{
return l;
}
//printf("%d\n", l);
}
int binary_upper(int temp){
int l = 0, r = n - 1;
bool flag = false;
while(l <= r){
int middle = (l + r) >> 1;
if(a[middle] > temp){
r = middle - 1;
}else{
if(a[middle] == temp){
flag = true;
}
l = middle + 1;
}
}
if(!flag){ //不存在,返回-1
return -1;
}else{
return l - 1; //返回l - 1
}
//printf("%d\n", l);
}
int main(){
int t;
scanf("%d%d", &n, &t);
for(int i = 0; i < n; ++i){
scanf("%d", &a[i]);
}
while(t > 0){
int temp;
scanf("%d", &temp);
int ans = binary_lower(temp);
if(ans == -1){
printf("-1 -1\n");
}else{
printf("%d %d\n", ans, binary_upper(temp));
}
t--;
}
return 0;
}
·【AcWing】·数的三次方根
题目链接:·【AcWing】·数的三次方根
- 求解方程
x
3
=
n
x^3 = n
x3=n
移项得 f ( x ) = x 3 − n f(x) = x^3 - n f(x)=x3−n - 于是可以不断二分
x
1
x_1
x1和
x
2
x_2
x2,零点定理(高等数学)得:
若 f ( x 1 ) ∗ f ( x 2 ) < 0 f(x_1) * f(x_2) < 0 f(x1)∗f(x2)<0,则存在 f ( x ) = 0 f(x) = 0 f(x)=0,其中x在 [ x 1 , x 2 ] [x_1, x_2] [x1,x2]内 - 题目要求x精确到小数点后六位,根据经验,二分100次足矣
————————————完整代码————————————
#include <cstdio>
using namespace std;
double n;
double solve(double x){
return x * x * x - n;
}
int main(){
scanf("%lf", &n);
double l, r;
if(n > 0){
l = 0;
r = 10000;
}else{
l = -10000;
r = 0;
}
for(int i = 0; i < 100; ++i){
double middle = (l + r) / 2;
if(solve(l)*solve(middle) < 0){ //根在(l, middle)
r = middle;
}else{ //根在(middle, r)
l = middle;
}
}
printf("%lf\n", l);
//printf("%lf %lf\n", l, r);
return 0;
}
·【蓝桥】·分巧克力
题目链接:·【蓝桥】·分巧克力
- 很容易想到是一道二分的题目,不妨temp最大就从10^5满足,需要二分多少次呢?
- 2^20 > 10 ^ 6,二分20次足矣
————————————完整代码————————————
#include <cstdio>
using namespace std;
const int maxn = 1e5 + 5;
struct note{
int a, b;
}s[maxn];
int n, k;
bool judge(int temp){
int ans = 0;
for(int i = 0; i < n; ++i){
ans += (s[i].a / temp) * (s[i].b / temp);
}
return ans >= k;
}
void solve(){ // 开始二分
int l = 0, r = maxn*2;
for(int i = 0; i < 20; ++i){
int middle = (l + r) >> 1;
if(judge(middle)){
l = middle;
}else{
r = middle;
}
}
printf("%d\n", l);
//printf("%d %d\n", l, r);
}
int main(){
scanf("%d%d", &n, &k);
for(int i = 0; i < n; ++i){
scanf("%d%d", &s[i].a, &s[i].b);
}
solve();
return 0;
}