今天我们继续训练二分的相关题目,大部分都是二分的题目,二分的模板我在上一篇博客中写了,如有需要可以参考。
题目描述
输入 n n n 个不超过 1 0 9 10^9 109 的单调不减的(就是后面的数字不小于前面的数字)非负整数 a 1 , a 2 , … , a n a_1,a_2,\dots,a_{n} a1,a2,…,an,然后进行 m m m 次询问。对于每次询问,给出一个整数 q q q,要求输出这个数字在序列中第一次出现的编号,如果没有找到的话输出 − 1 -1 −1 。
输入格式
第一行 2 2 2 个整数 n n n 和 m m m,表示数字个数和询问次数。
第二行 n n n 个整数,表示这些待查询的数字。
第三行 m m m 个整数,表示询问这些数字的编号,从 1 1 1 开始编号。
输出格式
输出一行, m m m 个整数,以空格隔开,表示答案。
样例 #1
样例输入 #1
11 3
1 3 3 3 5 7 9 11 13 15 15
1 3 6
样例输出 #1
1 2 -1
提示
数据保证, 1 ≤ n ≤ 1 0 6 1 \leq n \leq 10^6 1≤n≤106, 0 ≤ a i , q ≤ 1 0 9 0 \leq a_i,q \leq 10^9 0≤ai,q≤109, 1 ≤ m ≤ 1 0 5 1 \leq m \leq 10^5 1≤m≤105
本题输入输出量较大,请使用较快的 IO 方式。
解题思路
二分查找模板题,需要注意的是输入输出比较多,使用scanf和printf节省时间
代码
#include<iostream>
using namespace std;
const int N = 1e6+10;
int n,m;
int a[N];
int main(){
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++)scanf("%d",&a[i]);
int temp;
int l,r;
while(m--){
scanf("%d",&temp);
l=0,r=n-1;
while(l<r){
int mid=l+r>>1;
if(a[mid]>=temp)r=mid;
else l=mid+1;
}
if(a[l]==temp)cout<<l+1<<" ";
else cout<<-1<<" ";
}
}
- 019、P1102 A-B 数对
题目背景
出题是一件痛苦的事情!
相同的题目看多了也会有审美疲劳,于是我舍弃了大家所熟悉的 A+B Problem,改用 A-B 了哈哈!
题目描述
给出一串正整数数列以及一个正整数 C C C,要求计算出所有满足 A − B = C A - B = C A−B=C 的数对的个数(不同位置的数字一样的数对算不同的数对)。
输入格式
输入共两行。
第一行,两个正整数 N , C N,C N,C。
第二行, N N N 个正整数,作为要求处理的那串数。
输出格式
一行,表示该串正整数中包含的满足 A − B = C A - B = C A−B=C 的数对的个数。
样例 #1
样例输入 #1
4 1
1 1 2 3
样例输出 #1
3
提示
对于 75 % 75\% 75% 的数据, 1 ≤ N ≤ 2000 1 \leq N \leq 2000 1≤N≤2000。
对于 100 % 100\% 100% 的数据, 1 ≤ N ≤ 2 × 1 0 5 1 \leq N \leq 2 \times 10^5 1≤N≤2×105, 0 ≤ a i < 2 30 0 \leq a_i <2^{30} 0≤ai<230, 1 ≤ C < 2 30 1 \leq C < 2^{30} 1≤C<230。
2017/4/29 新添数据两组
解题思路
非常像我们力扣上最经典的两数之和,区别是这个是两数之差,思路是一样,使用哈希表优化时间,读数据时,用map存储每个数的个数,遍历一遍,寻找当前数的被减数个数,求和即答案。
不过这个思路好像和二分没什么关系。
我一开始做题的时候考虑一遍遍历,边存边计算答案,可是这样需要对数列排序,1e5最差情况的话快排会到1e10,会超时间,所以放弃了。
代码
#include<iostream>
#include<map>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N = 2e5 +10;
ll a[N];
int n;
ll c,ans;
map<ll,ll> mp;
bool cmp(ll a1,ll a2){
return a1>=a2;
}
int main(){
cin>>n>>c;
for(int i=0;i<n;i++){
cin>>a[i];
mp[a[i]]++;
}
for(int i=0;i<n;i++){
ans+=mp[a[i]+c];
}
cout<<ans;
}
题目描述
伐木工人 Mirko 需要砍 M M M 米长的木材。对 Mirko 来说这是很简单的工作,因为他有一个漂亮的新伐木机,可以如野火一般砍伐森林。不过,Mirko 只被允许砍伐一排树。
Mirko 的伐木机工作流程如下:Mirko 设置一个高度参数 H H H(米),伐木机升起一个巨大的锯片到高度 H H H,并锯掉所有树比 H H H 高的部分(当然,树木不高于 H H H 米的部分保持不变)。Mirko 就得到树木被锯下的部分。例如,如果一排树的高度分别为 20 , 15 , 10 20,15,10 20,15,10 和 17 17 17,Mirko 把锯片升到 15 15 15 米的高度,切割后树木剩下的高度将是 15 , 15 , 10 15,15,10 15,15,10 和 15 15 15,而 Mirko 将从第 1 1 1 棵树得到 5 5 5 米,从第 4 4 4 棵树得到 2 2 2 米,共得到 7 7 7 米木材。
Mirko 非常关注生态保护,所以他不会砍掉过多的木材。这也是他尽可能高地设定伐木机锯片的原因。请帮助 Mirko 找到伐木机锯片的最大的整数高度 H H H,使得他能得到的木材至少为 M M M 米。换句话说,如果再升高 1 1 1 米,他将得不到 M M M 米木材。
输入格式
第 1 1 1 行 2 2 2 个整数 N N N 和 M M M, N N N 表示树木的数量, M M M 表示需要的木材总长度。
第 2 2 2 行 N N N 个整数表示每棵树的高度。
输出格式
1 1 1 个整数,表示锯片的最高高度。
样例 #1
样例输入 #1
4 7
20 15 10 17
样例输出 #1
15
样例 #2
样例输入 #2
5 20
4 42 40 26 46
样例输出 #2
36
提示
对于 100 % 100\% 100% 的测试数据, 1 ≤ N ≤ 1 0 6 1\le N\le10^6 1≤N≤106, 1 ≤ M ≤ 2 × 1 0 9 1\le M\le2\times10^9 1≤M≤2×109,树的高度 ≤ 4 × 1 0 5 \le 4\times 10^5 ≤4×105,所有树的高度总和 > M >M >M。
解题思路
二分答案,思路就是先确定答案的最大值和最小值,然后二分。核心就在于check函数怎么写。
对于给定我们一个高度,我们要去判断这个高度能否得到我们需要的木材。我们遍历每一棵树,如果树的高度高于给定高度,就锯掉,sum记录获得木材的长度,sum加上锯掉的长度,注意:树高小于答案高度,sum不可加,因为此时是负数,如果sum大于等于需要的木材,则true。
代码
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1e6 +10;
int n;
long long m;
int a[N];
int h;
bool check(int mid){
long long sum=0;
for(int i=0;i<n;i++){
sum+=(a[i]-mid)>0?a[i]-mid:0;
if(sum>=m)return true;
}
return false;
}
int main(){
cin>>n>>m;
for(int i=0;i<n;i++)cin>>a[i];
int l=0,r=400000;
while(l<r){
int mid = (l+r+1)>>1;
if(check(mid)){
l=mid;
}else r=mid-1;
}
cout<<l;
}
题目描述
有形如: a x 3 + b x 2 + c x + d = 0 a x^3 + b x^2 + c x + d = 0 ax3+bx2+cx+d=0 这样的一个一元三次方程。给出该方程中各项的系数( a , b , c , d a,b,c,d a,b,c,d 均为实数),并约定该方程存在三个不同实根(根的范围在 − 100 -100 −100 至 100 100 100 之间),且根与根之差的绝对值 ≥ 1 \ge 1 ≥1。要求由小到大依次在同一行输出这三个实根(根与根之间留有空格),并精确到小数点后 2 2 2 位。
提示:记方程 f ( x ) = 0 f(x) = 0 f(x)=0,若存在 2 2 2 个数 x 1 x_1 x1 和 x 2 x_2 x2,且 x 1 < x 2 x_1 < x_2 x1<x2, f ( x 1 ) × f ( x 2 ) < 0 f(x_1) \times f(x_2) < 0 f(x1)×f(x2)<0,则在 ( x 1 , x 2 ) (x_1, x_2) (x1,x2) 之间一定有一个根。
输入格式
一行, 4 4 4 个实数 a , b , c , d a, b, c, d a,b,c,d。
输出格式
一行, 3 3 3 个实根,从小到大输出,并精确到小数点后 2 2 2 位。
样例 #1
样例输入 #1
1 -5 -4 20
样例输出 #1
-2.00 2.00 5.00
思路
我们发现,答案的范围比较小,从-100到100,精度0.01,我们直接for循环遍历也不会超时间。
我们以0.01为步长去遍历,找到答案就输出。
需要注意的是:我们在判断每一个点x时,如果func(x)==0,那么它一定是一个解,因为题目中说两个答案相差大于等于1,下一个我们直接遍历x+1.如果func(x)*func(x+0.01)<0,根据代码顺序,如果到这一步,说明x一定不是答案,所以此时的答案我们输出x+0.01.(具体看我的代码应该会更好理解)
代码
#include<iostream>
using namespace std;
double a,b,c,d;
double func(double x){
return a*x*x*x+b*x*x+c*x+d;
}
int main(){
cin>>a>>b>>c>>d;
double l=-100.0,r=100.0;
for(double i=l;i<=r;i+=0.01){
if(func(i)==0.0){
printf("%.2lf ",i);
i+=0.99;
}
else if(func(i)*func(i+0.01)<0){
//此时i一定不是答案,答案在(i,i+0.01]中,精度是0.01,所以答案输出i+0.01即可
printf("%.2lf ",i+0.01);
i+=0.99;
}
}
}
- 022、P1678 烦恼的高考志愿
题目背景
计算机竞赛小组的神牛 V 神终于结束了高考,然而作为班长的他还不能闲下来,班主任老 t 给了他一个艰巨的任务:帮同学找出最合理的大学填报方案。可是 v 神太忙了,身后还有一群小姑娘等着和他约会,于是他想到了同为计算机竞赛小组的你,请你帮他完成这个艰巨的任务。
题目描述
现有 m m m 所学校,每所学校预计分数线是 a i a_i ai。有 n n n 位学生,估分分别为 b i b_i bi。
根据 n n n 位学生的估分情况,分别给每位学生推荐一所学校,要求学校的预计分数线和学生的估分相差最小(可高可低,毕竟是估分嘛),这个最小值为不满意度。求所有学生不满意度和的最小值。
输入格式
第一行读入两个整数 m , n m,n m,n。 m m m 表示学校数, n n n 表示学生数。
第二行共有 m m m 个数,表示 m m m 个学校的预计录取分数。第三行有 n n n 个数,表示 n n n 个学生的估分成绩。
输出格式
输出一行,为最小的不满度之和。
样例 #1
样例输入 #1
4 3
513 598 567 689
500 600 550
样例输出 #1
32
提示
数据范围:
对于 30 % 30\% 30% 的数据, 1 ≤ n , m ≤ 1000 1\leq n,m\leq1000 1≤n,m≤1000,估分和录取线 ≤ 10000 \leq10000 ≤10000;
对于 100 % 100\% 100% 的数据, 1 ≤ n , m ≤ 100000 1\leq n,m\leq100000 1≤n,m≤100000,估分和录取线 ≤ 1000000 \leq 1000000 ≤1000000 且均为非负整数。
解题思路
核心题意就是根据学生的分数查找最相近的学校录取分,首先将学校的录取分数排序,对于每一个学生,我们可以进行两次二分查找,第一次查找>=学生成绩的最小值,第二次查找<=学生成绩的最大值,比较哪一个更近,更近的即为答案。
代码
#include<iostream>
#include<algorithm>
using namespace std;
const int N= 1e5 +10;
int n,m;
int s[N],a[N];
long long ans;
int main(){
cin>>m>>n;
for(int i=0;i<m;i++){
cin>>s[i];
}
for(int j=0;j<n;j++){
cin>>a[j];
}
sort(s,s+m);
for(int i=0;i<n;i++){
int l=0,r=m-1;
while(l<r){
int mid=l+r>>1;
if(s[mid]>=a[i]){
r=mid;
}else l=mid+1;
}
int temp =l;
l=0,r=m-1;
while(l<r){
int mid=l+r+1>>1;
if(s[mid]<=a[i]){
l=mid;
}else r=mid-1;
}
ans += min(abs(s[temp]-a[i]),abs(s[l]-a[i]));
}
cout<<ans;
}
总结
感觉二分的题目有一定的共性,一般是要找一个变量的最值,可以满足某个条件的最值,模板都一样,核心在于check函数的写法。
结语
放弃不一定丢人,但坚持下去一定很酷。