算法学习专栏——二分法
一、数的范围(简单)
给定一个按照升序排列的长度为 n 的整数数组,以及 q 个查询。
对于每个查询,返回一个元素 k 的起始位置和终止位置(位置从 00 开始计数)。
如果数组中不存在该元素,则返回 -1 -1。
输入格式
第一行包含整数 n 和 q,表示数组长度和询问个数。
第二行包含 n 个整数(均在 1∼10000 范围内),表示完整数组。
接下来 q 行,每行包含一个整数 k,表示一个询问元素。
输出格式
共 q 行,每行包含两个整数,表示所求元素的起始位置和终止位置。
如果数组中不存在该元素,则返回 -1 -1。
数据范围
1≤n≤100000
1≤q≤10000
1≤k≤10000
输入样例:
1 | |
输出样例:
1 | |
思路
直播(录播)
方法一(铁对)
答案(请自己先思考一下再参考)
#include < iostream>
using namespace std;
const int N = 1e6 + 10;
int arr[N];
int main()
{
int n,m;
cin >> n >> m;
for (int i = 0; i < n; i++) scanf("%d ",&arr[i]);
while (m--)
{
int x;
scanf("%d",&x);
int l = 0, r = n - 1;
while (l < r)
{
int mid = (l + r) / 2;
if (arr[mid] >= x) r = mid;
else l = mid + 1;
}
if (arr[l] != x) cout << "-1 -1" << endl;
else
{
printf("%d ",l);
int l = 0,r = n - 1;
while (l < r)
{
int mid =(l + r + 1) / 2;
if (arr[mid] <= x) l = mid;
else r = mid - 1;
}
printf("%d\n",l);
}
}
return 0;
}
方法二(目前没有发现特殊情况)
答案(请自己先思考一下再参考)
#include < iostream>
#include < algorithm>
using namespace std;
const int N = 1e5 + 10;
int n;
int a[N];
int main()
{
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int n, m;
cin >> n >> m;
for (int i = 0; i < n; i++) {
cin >> a[i];
}
while (m--){
int x;
cin >> x;
int l = -1, r = n;
while (l + 1!= r) {
int mid = l + r >> 1;
if (x > a[mid]) l = mid;
else r = mid;
}
if (r == n || a[r] != x)
{
cout << "-1 -1\n";
continue;
}
cout << r << " ";
l = -1, r = n;
while (l + 1!= r) {
int mid = l + r >> 1;
if (x < a[mid]) r = mid;
else l = mid;
}
cout << l << '\n';
}
return 0;
}
二、冶炼金属(简单++)
小蓝有一个神奇的炉子用于将普通金属 O 冶炼成为一种特殊金属 X。
这个炉子有一个称作转换率的属性 V,V 是一个正整数,这意味着消耗 V 个普通金属 O 恰好可以冶炼出一个特殊金属 X,当普通金属 O 的数目不足 V 时,无法继续冶炼。
现在给出了 N 条冶炼记录,每条记录中包含两个整数 A 和 B,这表示本次投入了 A 个普通金属 O,最终冶炼出了 B 个特殊金属 X。
每条记录都是独立的,这意味着上一次没消耗完的普通金属 O 不会累加到下一次的冶炼当中。
根据这 N 条冶炼记录,请你推测出转换率 V 的最小值和最大值分别可能是多少,题目保证评测数据不存在无解的情况。
输入格式
第一行一个整数 N,表示冶炼记录的数目。
接下来输入 N 行,每行两个整数 A、B、,含义如题目所述。
输出格式
输出两个整数,分别表示 V 可能的最小值和最大值,中间用空格分开。
数据范围
对于 30% 的评测用例,1≤N≤10^2^
对于 60% 的评测用例,1≤N≤10^3^
对于 100% 的评测用例,1≤N≤10^4^,1≤B≤A≤10^9^
输入样例:
1 | |
输出样例:
1 | |
样例解释
当 V=20=20 时,有:⌊75/20⌋=3,⌊53/20⌋=2,⌊59/20⌋=2,可以看到符合所有冶炼记录。
当 V=25=25 时,有:⌊75/25⌋=3,⌊53/25⌋=2,⌊59/25⌋=2,可以看到符合所有冶炼记录。
且再也找不到比 20 更小或者比 25 更大的符合条件的 V 值了。
思路
方法一 就不介绍了,就是最基本的二分思路,两个模板。
方法二 主要是按照数学公式推导的方法来写的,推到过程如下:


代码
方法一
答案(请自己先思考一下再参考)
#include < iostream>
using namespace std;
int N,x,y,Max = 1e9;
const int n = 10010;
int arr[n],ans[n];
int check(int x)
{
for (int i = 0; i < N; i ++ )
{
if(ans[i] < arr[i] / x)
return 2;
if (ans[i] > arr[i] / x)
return 3;
}
return 1;
}
int main()
{
cin >> N;
for (int i = 0; i < N; i++) cin >> arr[i] >> ans[i];
int left = 1,right = Max;
while(left < right)
{
int mid = (left + right) >> 1;
if (check(mid) == 1 || check(mid) == 3) right = mid;
else left = mid + 1;
}
cout << left << " ";
left = 1;
right = Max;
while(left < right)
{
int mid = (left + right + 1) >> 1;
if (check(mid) == 2 || check(mid) == 1) left = mid;
else right = mid - 1;
}
cout << left;
return 0;
}
方法二
答案(请自己先思考一下再参考)
#include < iostream>
using namespace std;
int main()
{
long long n = 0,a,b,c=0x3f3f3f3f,d=0;
cin >> n;
while (n -- )
{
cin >> a >> b;
c = min(c,a/b);
d = max(d,a/(b+1) + 1);
}
cout << d << " " << c;
return 0;
}
三、牛可乐和魔法封印(扩充)
牛可乐得到了一个长度为 nnn 且非严格单调递增的序列 aaa,然而这个序列被 qqq 层魔法封印了,其中第 iii 层封印的问题包含两个整数 xi,yi(xi≤yi)x_i,y_i(x_i\leq y_i)xi,yi(xi≤yi),牛可乐必须正确回答序列中大于等于 xix_ixi且小于等于 yiy_iyi 的数字个数才能够解开该层封印。
牛可乐觉得这个问题太难了,于是他想请你帮助他解开序列的 qqq 层封印。
输入描述:
$$
第一行包含一个整数 n(1≤n≤10^5),表示序列的长度。\
第二行包含 n 个整数,其中 −10^9≤a_i≤10^9\
第三行包含一个整数 q(1≤q≤10^5),代表封印层数。\
之后 q 行,每行两个整数 x_i,y_i(-10^9\leq x_i\leq y_i\leq 10^9),代表该层封印的询问。
$$
输出描述:
$$
对于每层封印,输出一行一个整数,代表在范围内的数字个数。
$$
输入
1 | |
输出
1 | |
说明
1 | |
示例2
输入
1 | |
输出
1 | |
代码
答案(请自己先思考一下再参考)
#include < iostream>
#include < algorithm>
using namespace std;
const int N = 1e5 + 10;
int n, q;
int a[N];
int main()
{
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
cin >> q;
while (q--) {
int x, y;
cin >> x >> y;
int l = 1, r = n + 1;
while (l < r) {
int mid = l + r >> 1;
if (a[mid] >= x) r = mid;
else l = mid + 1;
}
int ll = 0, rr = n;
while (ll < rr) {
int mid = (ll + rr + 1) >> 1;
if (a[mid] <= y) ll = mid;
else rr = mid - 1;
}
cout << rr - l + 1 << '\n';
}
return 0;
}