二分是一种十分高效的工具,使用二分可以节省大量的时间。我们首先通过一个小练习来初步熟悉一下二分(转自YCOJ):
请问最后将输出什么呢?
答案是4343(两个43)。
通过这些,相信读者已经对二分有了初步的认识。接下来做一些二分的习题:
查找
Description
给出一个有 n 个元素的数列 a 和两个整数 k 和 s,其中数列 a 的元素是按照升序排列的。
请你在数列中找出一个元素 x,使得的 x+k=s。
Input
输入第一行一个整数 n(1≤n≤10^5),表示数列中的元素个数。
接下来一行输入 n 个空格隔开的整数,表示输入的数列 a,保证是升序排列,并且 −10^9≤ai≤10^9。
接下来一行输入两个整数 k,s(−10^9≤k,s≤10^9)。
Output
如果能找到满足条件的数,输出"Yes",否者输出"No"。
输入k、s之后,就能够算出x的值,然后用二分查找的方式在a数组里查找x是否存在就可以了。代码如下:
#include<iostream>
using namespace std;
int main () {
int n;
cin >> n;
int a [n];
for(int i = 0; i < n; i++){
cin >> a [i];
}
int k, s;
cin >> k >> s;
int l = 0, r = n - 1, ans = 0, x = s - k;
while(l < r){
int mid = (l + r) / 2;
if (a [mid] < x){
l = mid + 1;
}
else{
r = mid;
}
}
if(a[l] == x) ans = 1;//当二分结束后,l和r一定是相等的
if(ans) cout << "Yes" << endl;
else cout << "No" << endl;
return 0;
}
两数之和
Description
给出一个有 n 个元素的数列 a 和一个整数 s,其中数列 a 的元素是按照升序排列的。
请你在数列中找出两个元素 x,y使得的 x+y=s。
Input
输入第一行一个整数 n(1≤n≤10^5),表示数列中的元素个数。
接下来一个输入 n 个空格隔开的整数,表示输入的数列 a,保证是升序排列,并且 −10^9≤ai≤10^9。
接下来一行输入一个整数 s(−10^9≤x,s≤10^9)。
Output
如果能找到满足条件的两个数,输出"Yes",否者输出"No"。
这题和前面那题差别不大,在二分外面套一个循环就可以了:
#include<iostream>
using namespace std;
int main () {
long long n;
cin >> n;
int a [n];
for(int i = 0; i < n; i++) cin >> a [i];
int s;
cin >> s;
int ans = 0;
for(int i = 0; i < n; i++){
int l = 0, r = n - 1, x = s - a [i];
while(l < r){
int mid = (l + r) / 2;
if (a [mid] < x) l = mid + 1;
else r = mid;
if (l != i && r != i && a [l] == x && a [r] == x){
ans = 1;
break;
}
}
if (ans) break;
}
if (ans == 1) cout << "Yes" << endl;
else cout << "No" << endl;
return 0;
}
报数游戏
Description
小信在和他的朋友们一起玩一个游戏。由于小信的机智,这个游戏由小信担任裁判。
首先,小信会给每个朋友一个唯一的编号。接下来的每一回合,小信会说一个数,编号不超过它的最大编号的人要报出自己的编号。如果没有人的编号比小信给出的数小,那么编号最小的人要报出自己的编号。每个人可以重复报号。
小信会按照一个列表顺次说出每个回合的数,他的朋友们想知道每回合该报出的编号应该是多少。你能帮帮他们吗?
Input
输入数据共 3 行。第一行有两个整数 n,m(1≤n≤10^5,1≤m≤10^5),分别表示参与游戏的小信朋友的个数,和游戏的回合数。
第二行 n 个整数 ai(1≤ai≤10^8),表示朋友们每个人的编号。对于 0≤i<j<n,都有 ai<aj,即他们的编号递增排列。
第三行 m 个整数 qi(1≤qi≤108),表示每回合小信给的数字。
Output
输出共一行 m 个整数,表示每回合报出的编号,每两个整数之间一个空格,最后一个数后面没有空格。
对于q数组的每一个元素,都在a数组里二分地查找一下小于它的最大的那个数,然后对于q数组中小于a数组最小值的数和大于a数组中最大值的数进行特殊判断就可以了:
#include<iostream>
using namespace std;
const int maxn = 100000 + 5;
int a[maxn], q[maxn];
int main (){
int n, m;
cin >> n >> m;
for(int i=0; i<n; i++) cin >> a[i];
for(int i=0; i<m; i++) cin >> q[i];
bool flag = 0;
for (int i = 0; i < m; i++){
int ans;
if (a[0] > q[i]) ans = a[0];
else if (a[n - 1] < q[i]) ans = a[n - 1];
else{
int l = 0, r = n - 1;
while (l < r){
int mid = (l + r) / 2;
if (a[mid] <= q[i]) l = mid + 1;
else r = mid;
}
ans = a[l - 1];
}
if (flag) cout << " ";
flag = 1;
cout << ans;
}
return 0;
}
由于题目规定了输入的数据是天然排序的,所以我们不需要对数组再进行排序来寻找最大值。
方程求解
Description
有一个如下的方程需要你帮忙计算一下。
xe^x=a
其中,e 为自然底数,a 为一个常数。
请你求出满足等式的 x 的值。
Input
输入一个整数 a(0≤a≤10^9)。
Output
输出一个浮点数,表示方程的解,四舍五入到小数点后四位。
自然对数e^x 可以使用数学库中函数exp(x) 来计算;可以通过 while (r - l > 1e-6 ) { } 的方法来控制精度。
#include<bits/stdc++.h>
using namespace std;
int main(){
int a;
cin >> a;
double l = 0, r = 18, mid, temp;
while(r - l > 1e-6){
mid = (r + l) / 2;
temp = mid * exp(mid);
if(temp < a) l = mid;
else r = mid;
}
printf("%.4lf", mid);
return 0;
}
这里用了C语言的printf来控制输出的位数。
割绳子
Description
现有 N 条绳子,它们的长度分别为 L1,L2,……,Ln,如果从它们中切割出 K 条长度相同的绳子,这 K 条绳子每条最长能有多长?
Input
输入共有两行,第一行包含两个正整数 N 和 K(1≤N≤1000,1≤K≤1000),用一个空格分隔;第二行包含 N 个数,依次表示 N 条绳子的长度,两数间用一个空格分隔。每条绳子的长度的小数不超过两位(1≤Li≤10000)。
Output
输出仅包含一个数,表示所得 K 条绳子的最大长度。答案四舍五入保留小数点后两位。
这题只需要找出绳子中的最大值,然后在0和最大值之间用二分查找就可以解了:
#include<bits/stdc++.h>
using namespace std;
int main(){
int n, k;
cin >> n >> k;
double line[n], maxn = -52697;
for(int i = 0; i < n; i++){
cin >> line[i];
if(line[i] > maxn) maxn = line[i];
}
double l = 0, r = maxn, mid;
while(r - l > 1e-4){
mid = (r + l) / 2;
int temp = 0;
for(int i = 0; i < n; i++) temp+= floor(line[i] / mid);
if(temp >= k) l = mid;
else r = mid;
}
printf("%.2lf", mid + 1e-6);//加上一个较小的值可以防止四舍五入出错
return 0;
}
注意这里也需要用一个较小的数控制精度。这里用的是1e-4。