2025CSP-J 冲刺训练(1):二分

一、二分查找函数

1. 头文件

二分查找函数所在的头文件是算法头文件:

#include<algorithm>

2. 前提条件

传入的参数数组 a[] 必须是一个有序的数组,且一定是从小到大排列的。

3. 功能函数

3.1 lower_bound

lower_bound(a+1,a+n+1,val)-a;
程序意义
a+1起始位置指针
a+n+1哨兵位置指针
lower_bound()返回了一个指针
整体返回 a[] 中第一个值 ≥ v a l \ge \tt val val 的元素下标

3.2 upper_bound

功能:返回 a[] 中第一个值 > v a l > \tt val >val 的元素下标。

二、二分答案模板

1. 前提条件

  1. 题目要求直接求答案,且只能用枚举、暴力或者恶心的方法求解
  2. 检查一个答案很简单,且答案和限制条件是单调的

2. 模板

//最小值
while(l<r){
    //l+r可能溢出
    int mid=l+(r-l>>1);//mid=(l+r)/2
    if(check(mid))r=mid;
    else l=mid+1;
}
cout<<l;
//最大值
while(l<r){
    int mid=l+(r-l+1>>1);//mid=(l+r+1)/2
    if(check(mid))l=mid;
    else r=mid-1;
}
cout<<l;

三、典型例题

1. 寻找固定的和

1.1 审题

给出两个数组 x[]y[] 和一个整数 k k k,每次从两个数组中各自取出一个数 x i x_i xi y j y_j yj,要求满足 x i + y j = k x_i+y_j=k xi+yj=k。已经取出过的数不能再取,问最多可以取出几对数?

1.2 分析

根据特性可知, [ [ [a[lower_bound(a+1,a+n+1,val)-a] , , ,a[upper_bound(a+1,a+n+1,val)-a] ) ) ) 的范围中所有的数字都是 v a l \tt val val。则可以使用 a[upper_bound(a+1,a+n+1,val)-a] − - a[lower_bound(a+1,a+n+1,val)-a] 来计算 v a l \tt val val 有多少个。

1.3 参考答案

60 60 60 分代码(未考虑 x[]y[] 的重复):

#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+8;
int n,k,x[MAXN],y[MAXN];
int main(){
    cin>>n>>k;
    for(int i=1;i<=n;i++)cin>>x[i];
    for(int i=1;i<=n;i++)cin>>y[i];
    //二分函数要保证序列有序,所以要先排序
    sort(x+1,x+n+1);
    sort(y+1,y+n+1);
    long long ans=0;
    for(int i=1;i<=n;i++)//遍历一遍x[]中所有的元素
        ans+=upper_bound(y+1,y+n+1,k-x[i])-lower_bound(y+1,y+n+1,k-x[i]);//获取y[]中所有和x[i]相加为k的元素
    cout<<ans;
    return 0;
}

100 100 100 分代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+8;
int n,k,x[MAXN],y[MAXN];
int main(){
    cin>>n>>k;
    for(int i=1;i<=n;i++)cin>>x[i];
    for(int i=1;i<=n;i++)cin>>y[i];
    sort(x+1,x+n+1);
    sort(y+1,y+n+1);
    long long ans=0;
    for(int i=1;i<=n;i++)
        if(x[i]!=x[i-1])//x第一次出现
            ans+=min(upper_bound(y+1,y+n+1,k-x[i])-
                     lower_bound(y+1,y+n+1,k-x[i]),
                     upper_bound(x+1,x+n+1,x[i])-
                     lower_bound(x+1,x+n+1,x[i]));
    cout<<ans;
    return 0;
}

if 的判断可以有效地筛去 x[] 中重复的数值。
min 函数的第一个参数表示 y[] 中所有的 k-x[i]
min 函数的第二个参数表示 x[] 中所有的 x[i]
因为每个数字不能重复取出,所以取一个最小值。
如果实在看不懂,可以看看这个样例: k = 22 , x i = 10 , y = { 12 , 12 , 12 , 12 , 12 } k=22,x_i=10,y=\{12, 12, 12, 12, 12\} k=22,xi=10,y={12,12,12,12,12}很显然,按照 60 60 60 分的代码,我们算作了 5 5 5 对,这样 10 10 10 就会被重复取出。所以取它们重复个数的最小值算作最大的对数。

2. Snuke Festival

2.1 审题

今年音乐节,CCF(别问我为什么是 CCF)开始准备舞台。 舞台由上中下 3 3 3 部分组成。舞台 3 3 3 个部分的零件分别有 N N N 个,编号为 1 ∼ N 1\sim N 1N。第 i i i 个上部、中部、下部零件的尺寸分别为 A i , B i , C i A_i,B_i,C_i Ai,Bi,Ci。为了组成一个舞台,中部零件的尺寸必须比上部零件尺寸大,下部零件的尺寸必须比中部零件尺寸大。CCF 能够组成多少个不同的舞台? 相同的舞台是指上中下部编号对应相等的舞台。

2.2 分析

说人话,这道题目就是:
给定三个长度为 n n n 的数组 A , B , C A,B,C A,B,C,找出所有满足 A i < B j < C k A_i<B_j<C_k Ai<Bj<Ck 的数字,且三个数字之前都未被使用。

2.3 参考答案

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+8;
int n,a[MAXN],b[MAXN],c[MAXN];
int main(){
    cin>>n;
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1;i<=n;i++)cin>>b[i];
    for(int i=1;i<=n;i++)cin>>c[i];
    sort(a+1,a+n+1);
    sort(b+1,b+n+1);
    sort(c+1,c+n+1);
    long long ans=0;
    for(int i=1;i<=n;i++){//遍历所有的b[i]
        int cnta=lower_bound(a+1,a+n+1,b[i])-(a+1);
        int cntc=(c+n+1)-upper_bound(c+1,c+n+1,b[i]);
        ans+=1ll*cnta*cntc;
    }
    cout<<ans;
    return 0;
}

四、拓展例题

1. 晒衣服

1.1 审题

一件衣服在自然条件下用一秒的时间可以晒干 a a a 点湿度。抠门的熊大妈只买了一台烘衣机 。使用用一秒烘衣机可以让一件衣服额外烘干 b b b 点湿度(一秒晒干 a + b a+b a+b 湿度),但在同一时间内只能烘一件衣服。现在有 n n n 件衣服,第 i i i 衣服的湿度为 w i w_i wi(保证互不相同),要你求出弄干所有衣服的最少时间(湿度为 0 0 0 为干 )。

1.2 分析

详见注释。

1.3 参考答案

#include<bits/stdc++.h>
using namespace std;
const int MAXN=5e5+8;
int n,a,b,w[MAXN];
bool check(int ans){
    int cnt=0;
    for(int i=1;i<=n;i++){
        int done=ans*a;//计算使用烘干机后的湿度
        int rem=w[i]-done;//计算剩余湿度
        if(rem>0)cnt+=(rem+b-1)/b;//计算需要额外烘干的次数
    }
    return cnt<=ans;
}
int main(){
    cin>>n>>a>>b;
    int l=0,r=0;
    for(int i=1;i<=n;i++)
        cin>>w[i],r=max(r,(w[i]-1)/a+1);//r:计算最大烘干次数
    while(l<r){
        int mid=l+(r-l>>1);
        if(check(mid))r=mid;
        else l=mid+1;
    }
    cout<<l;
    return 0;
}

2. Chocolate Eating S

2.1 审题

Bessie 收到了来自公牛们的 N N N 1 ≤ N ≤ 50 , 000 1\le N\le50,000 1N50,000)块巧克力,但她不想吃得太快,所以她想要为接下来的 D D D 1 ≤ D ≤ 50 , 000 1\le D\le 50,000 1D50,000)天制定一个吃巧克力的计划,以最大化她在这段时间内的最小幸福值。
Bessie 的幸福值是一个整数,初始为 0 0 0 ,每天晚上睡觉时会减半(如果需要,向下取整)。然而,当她吃第 i i i 块巧克力时,她的幸福值会增加整数 H i H_i Hi 1 ≤ H i ≤ 1 , 000 , 000 1\le H_i\le1,000,000 1Hi1,000,000)。如果她在某一天吃巧克力,她这一天的幸福值被认为是吃完巧克力后的幸福值。Bessie 坚持要按照她收到巧克力的顺序来吃。
如果存在多个最优解,输出其中任意一个即可。
考虑一个例子,Bessie 需要在 5 5 5 天内吃 5 5 5 块巧克力,它们分别带来的幸福值为( 10 10 10 40 40 40 13 13 13 22 22 22 7 7 7)。
如果 Bessie 在第一天吃第一块巧克力(幸福值 10 10 10),然后等待吃其他的巧克力,她在第一天的幸福值是 10 10 10
最小的睡前幸福值是 24 24 24,这是 Bessie 能做到的最优结果。

2.2 参考答案

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN=5e4+8;
int n,d,h[MAXN],t[MAXN];
bool check(ll ans, bool record){
    ll id=0,sum=0;
    for(int i=1;i<=d;i++,sum>>=1)
        while(sum<ans){
            if(id>n)return false;
            sum+=h[id];
            if(record)t[id]=i;
            id++;
        }
    return true;
}
int main(){
    cin>>n>>d;
    for(int i=1;i<=n;i++)cin>>h[i];
    ll l=0,r=5e10;
    while(l<r){
        ll mid=l+(r-l+1>>1);
        if(check(mid,false))l=mid;
        else r=mid-1;
    }
    cout<<l<<endl;
    check(l,true);
    for(int i=1;i<=n;i++)
        cout<<(t[i]?t[i]:d)<<endl;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值