暑假算法刷题日记 Day 5

今天我们继续训练二分的相关题目,大部分都是二分的题目,二分的模板我在上一篇博客中写了,如有需要可以参考。

题目描述

输入 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 1n106 0 ≤ a i , q ≤ 1 0 9 0 \leq a_i,q \leq 10^9 0ai,q109 1 ≤ m ≤ 1 0 5 1 \leq m \leq 10^5 1m105

本题输入输出量较大,请使用较快的 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<<" ";
    }
}

题目背景

出题是一件痛苦的事情!

相同的题目看多了也会有审美疲劳,于是我舍弃了大家所熟悉的 A+B Problem,改用 A-B 了哈哈!

题目描述

给出一串正整数数列以及一个正整数 C C C,要求计算出所有满足 A − B = C A - B = C AB=C 的数对的个数(不同位置的数字一样的数对算不同的数对)。

输入格式

输入共两行。

第一行,两个正整数 N , C N,C N,C

第二行, N N N 个正整数,作为要求处理的那串数。

输出格式

一行,表示该串正整数中包含的满足 A − B = C A - B = C AB=C 的数对的个数。

样例 #1

样例输入 #1

4 1
1 1 2 3

样例输出 #1

3

提示

对于 75 % 75\% 75% 的数据, 1 ≤ N ≤ 2000 1 \leq N \leq 2000 1N2000

对于 100 % 100\% 100% 的数据, 1 ≤ N ≤ 2 × 1 0 5 1 \leq N \leq 2 \times 10^5 1N2×105 0 ≤ a i < 2 30 0 \leq a_i <2^{30} 0ai<230 1 ≤ C < 2 30 1 \leq C < 2^{30} 1C<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 1N106 1 ≤ M ≤ 2 × 1 0 9 1\le M\le2\times10^9 1M2×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;
        
    	}
	}
}

题目背景

计算机竞赛小组的神牛 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 1n,m1000,估分和录取线 ≤ 10000 \leq10000 10000

对于 100 % 100\% 100% 的数据, 1 ≤ n , m ≤ 100000 1\leq n,m\leq100000 1n,m100000,估分和录取线 ≤ 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函数的写法。

结语

放弃不一定丢人,但坚持下去一定很酷。
请添加图片描述

  • 21
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值