二分(洛谷)

目录

【深基13.例1】查找

眼红的Medusa

A-B 数对 

银行贷款

烦恼的高考志愿

Cellular Network

Eating Queries

[COCI 2011/2012 #5] EKO / 砍树

木材加工

切绳子

[NOIP2015 提高组] 跳石头

[USACO06DEC]River Hopscotch S

进击的奶牛

数列分段 Section II 

[TJOI2007]路标设置

奶牛晒衣服

kotori的设备

卡牌

放书

Chat Ban

Binary String


【深基13.例1】查找

代码1:手打二分(要记住二分模板

#include<iostream>
using namespace std;
const int N=1e6+10;
int s[N];
int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=0;i<n;i++)cin>>s[i];
    while(m--)
    {
        int q;
        cin>>q;
        int l=0,r=n-1;
        while(l<r)
        {
            int mid=l+r>>1;
            if(s[mid]>=q)r=mid;
            else l=mid+1;
        }
        if(s[l]==q)cout<<l+1<<' ';
        else cout<<"-1"<<' ';
    }
    return 0;
}

代码2:STL(lower_bound

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int a[N];
int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=0;i<n;i++) cin>>a[i];
    while(m--)
    {
        int x;
        cin>>x;
        int b=lower_bound(a,a+n,x)-a;   //返回不小于目标值的第一个元素的下标
        if(a[b]==x)cout<<b+1<<" ";
        else cout<<"-1"<<" ";
    }
    return 0;
}

眼红的Medusa

注意是按在科技创新奖获奖名单中的先后次序输出

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int a[N],b[N];
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>>b[i];
    sort(b,b+m);
    for(int i=0;i<n;i++)
    {
        int x=lower_bound(b,b+m,a[i])-b;
        if(b[x]==a[i]) cout<<a[i]<<' ';
    }
    return 0;
}

A-B 数对

如果这个数组是有序的,那么对于每一个A的值,在它的后方就只有一个数值B满足A,B的差值为C。这时我们只需要使用两个函数求出数组中对于每个A,A+C的这两个位置它们的差即为数组中数值为A+C的元素个数。将这个数加到res中。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1e6;
int s[N];
int main()
{
    int n,c;
    cin>>n>>c;
    for(int i=0;i<n;i++)cin>>s[i];
    sort(s,s+n);
    long long res=0;
    for(int i=0;i<n;i++)res+=upper_bound(s,s+n,s[i]+c)-lower_bound(s,s+n,s[i]+c);//题目要求:(不同位置的数字一样的数对算不同的数对)。
    cout<<res;
    return 0;
}

银行贷款

关键是要知道怎么求利息,对于这道题了类型可以参看一下数的三次方根

代码:

#include<bits/stdc++.h>
using namespace std;
double n,m,q;    //注意数据类型!!!!,pow(double,double)
bool check(double x){
	return (pow(1.0/(1.0+x),q)>=1-n/m*x);
}
int main()
{

    cin>>n>>m>>q;
    double l=0,r=10;
    while(r-l>1e-8)
    {
        double mid=(l+r)/2;
        if(check(mid)) r=mid;
        else l=mid;
    }
    double res=l*100;
    printf("%.1f",res);
   // cout<<fixed<<setprecision(1)<<l*100;  输出一位小数
    return 0;
}

烦恼的高考志愿

二分:对于每一个成绩,二分找出第一个大于等于该成绩的数,相差最小的出现在左边的数小于该成绩,右边的数大于该成绩。

代码1:手打二分

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int a[N],b[N];
int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=0;i<n;i++)cin>>a[i];
    sort(a,a+n);
    for(int i=0;i<m;i++)cin>>b[i];
    int ans=0;
    for(int i=0;i<m;i++)
    {
        int l=0,r=n;
        while(l<r)
        {
            int mid=l+r>>1;
            if(a[mid]<=b[i])l=mid+1;
            else r=mid;
        }
        if(a[0]>=b[i])ans+=a[0]-b[i];   //有可能找不到返回最左边的值,所以要判断一下
        else ans+=min(abs(a[l]-b[i]),abs(a[l-1]-b[i]));
    }
    cout<<ans;
    return 0;
}

代码2:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int a[N];
int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=0;i<n;i++)cin>>a[i];
    sort(a,a+n);
    int ans=0;
    for(int i=0;i<m;i++)
    {
        int x;
        cin>>x;
        int s=lower_bound(a,a+n,x)-a;

        if(a[0]>=x)ans+=a[0]-x;
        else ans+=min(abs(a[s]-x),abs(a[s-1]-x));
    }
    cout<<ans;
    return 0;
}

Cellular Network

对于每一个城市,覆盖它的必然是x坐标与它最相近的两座塔之一,那么我们就可以二分来用城市找塔,在相邻两塔与它的距离中取min,再取所有城市的max就是答案了

代码:

#include<bits/stdc++.h>
using namespace std;
int a[100005], b[100005];
int n, m;
int main() {
    int ans = 0;
    cin >> n >> m;

    for (int i = 1; i <= n; i++)cin >> a[i];

    for (int i = 1; i <= m; i++)cin >> b[i];

    sort(a + 1, a + 1 + n);
    sort(b + 1, b + 1 + m);
    a[n + 1] = b[m + 1] = 1e9,a[0] = b[0] = -1e9;  //设个边界,防止溢出。

    for (int i = 1; i <= n; i++) 
    {
        int x;

        x = lower_bound(b + 1, b + 1 + m, a[i]) - b;

        ans = max(ans,min(b[x] - a[i],a[i] - b[x - 1]));
    }

    cout << ans;
    return 0;
}

max()赋给int变量 

Eating Queries

根据题意,可以优先吃最大的糖果,将数组从大到小进行排列,求数组的前缀和,则只需要找到第一个 ​≥x即可。(下标即代表最少吃糖的个数)

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int a[N],s[N];

bool cmp(int a,int b)
{
    return a>b;
}

int main()
{
    int t;
    cin>>t;
    while(t--)
    {
        int n,q;
        cin>>n>>q;
        for(int i=1;i<=n;i++)cin>>a[i];
        sort(a+1,a+n+1,cmp);

        for(int i=1;i<=n;i++)s[i]=s[i-1]+a[i];
        while(q--)
        {
            int x;
            cin>>x;
            if(s[n]<x)cout<<"-1"<<endl;
            else cout<<lower_bound(s+1,s+n+1,x)-s<<endl;
        }
    }
    return 0;
}

[COCI 2011/2012 #5] EKO / 砍树

二分答案问题:提高伐木机的高度,显然地,得到的木头会减少,同样地,放低得到的木头会增多,而正因为答案有单调性,所以我们可以使用二分。

选定一个答案mid,对数组进行遍历,将高出答案的部分进行累加累加的和与木材总长度比较进行区间更新。

代码:(学到一个求数组最大元素的小技巧)

#include<bits/stdc++.h>
typedef long long LL;
LL n,m,x,l,r,mid,s;   //注意需要用long long
const int N=1e6+10;
int a[N];
using namespace std;
int main()
{
    cin>>n>>m;
    for(int i=0;i<n;i++)cin>>a[i];
    x= *max_element(a,a+n);    //取最大值定义为右边界
    l=0,r=x;
    while(l<r)
    {
        mid=l+r>>1;
        s=0;
        for(int i=0;i<n;i++)
            if(a[i]>mid)s+=a[i]-mid;   //高出的部分进行累加
        if(s<m)r=mid;    //高出总木材的长度时
        else l=mid+1;
    }
    cout<<l-1;
    return 0;
}

木材加工

切割的长度进行二分,得到的切割数量输入的需要得到的小段的数量比较进行区间更新

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=1e5+10;
LL a[N];
LL n,k;

bool check(LL x)
{
    LL num=0;
    for(int i=0;i<n;i++)
    {
        num+=a[i]/x;  //c++自带整除,也可以用floor
    }
    return num>=k;
}

int main()
{
    cin>>n>>k;
    for(int i=0;i<n;i++)cin>>a[i];
    LL l=0,r=*max_element(a,a+n); 
    while(l<r)
    {
        LL mid=l+r+1>>1;
        if(check(mid)) l=mid;  //切得多或刚好等于,则说明还有可能长度更长
        else r=mid-1;    //切得少,说明长度太大了
    }
    cout<<l;
    return 0;
}

切绳子

和上面木材加工类似

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=1e5+10;
double a[N];
double n,k,l,r;

bool check(double x)
{
    double num=0;
    for(int i=0;i<n;i++)
    {
        num+=floor(a[i]/x);  //c++自带整除,也可以用floor
    }
    return num>=k;
}

int main()
{
    cin>>n>>k;
    for(int i=0;i<n;i++)cin>>a[i],r+=a[i];

    for(int i=0;i<100;i++)   //注意不是用while(r-l>1e-8)否则只得93,因为精度不够,需要多循环几次
    {
        double mid=(l+r)/2;
        if(check(mid)) l=mid;  //切得多或刚好等于,则说明还有可能长度更长
        else r=mid;    //切得少,说明长度太大了
    }
    double s=int(l*100);   //保留两位小数
    //double a=s/100.0;
    printf("%.2f",s/100.0);
    return 0;
}

[NOIP2015 提高组] 跳石头

二分答案:最小距离的最大值——最小距离一定出现在相邻石头之间。从起点出发,先选定一段距离mid,若前面的石头B与你所站着的石头A的距离小于mid(因为mid是最小的,如果有比mid小的就搬掉使其变大),就把B搬掉,记录一下;如果不,就把B留下,再跳到石头B上。照这个步骤多次循环后,如果搬掉和m比较,如果多了,就把距离mid定小点;如果少了,就把mid定大点。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
LL L,n,m,l,r,mid;
const int N=50010;
int a[N];
int main()
{
    cin>>L>>n>>m;
    for(int i=1;i<=n;i++)cin>>a[i];
    a[n+1]=L;   //注意是n+1
    l=0,r=L;
    int ans=0;
    while(l<r)
    {
        mid=l+r+1>>1;
        int now=0,k=0;  //now代表跳石头的人当前在什么位置
        for(int i=1;i<=n+1;i++)   //注意n+1
        {
            if(a[i]-a[now]<mid) k++;  //判定成功,把这块石头拿走,继续考虑下一块石头
            else now=i;  //判定失败,这块石头不用拿走,我们就跳过去,再考虑下一块
        }
        if(k<=m)l=mid,ans=mid;  //记录答案(更新中) 
        else r=mid-1;
    }
    cout<<ans;
    return 0;
}

[USACO06DEC]River Hopscotch S

同跳石头

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
LL L,n,m,l,r,mid;
const int N=50010;
int a[N];
int main()
{
    cin>>L>>n>>m;
    for(int i=1;i<=n;i++)cin>>a[i];
    sort(a+1,a+1+n);
    a[n+1]=L;   //注意是n+1
    l=0,r=L;
    int ans=0;
    while(l<r)
    {
        mid=l+r+1>>1;
        int now=0,k=0;
        for(int i=1;i<=n+1;i++)   //注意n+1
        {
            if(a[i]-a[now]<mid) k++;
            else now=i;
        }
        if(k<=m)l=mid,ans=mid;
        else r=mid-1;
    }
    cout<<ans;
    return 0;
}

进击的奶牛

同跳石头

代码:

#include<bits/stdc++.h>
using namespace std;
int n,c,l,r,mid;
const int N=1e5+10;
int a[N];
int main()
{
    cin>>n>>c;
    for(int i=1;i<=n;i++)cin>>a[i];
    sort(a+1,a+n+1);
    l=0,r=a[n]-a[1];
    while(l<r)
    {
        mid=l+r>>1;
        int now=1,k=1;
        
        for(int i=1;i<=n;i++)
        {
            if(a[i]-a[now]>=mid) k++,now=i;   //如果两个隔间的距离大于等于mid时,就在i上一头牛,然后当前的位子改为i
        }
        if(k<c) r=mid;
        else l=mid+1;
    }
    cout<<l-1;
    return 0;
}

数列分段 Section II 

二分答案:对于二分的值mid,我们从数列a从前往后扫,如果区间总和t大于了mid,我们不加而是t重新赋值并且隔板num++,最后只需判断num是否不小于m就行了

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int a[N];
int n,m,l,r,mid;
bool check(int x)
{
    int t=0,num=1;
    for(int i=1;i<=n;i++)
    {
        if(t+a[i]<=x) t+=a[i];
        else t=a[i],num++;
    }
    return num<=m;
}

int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)cin>>a[i],l=max(a[i],l),r+=a[i];
    while(l<r)
    {
        mid=l+r>>1;
        if(check(mid))r=mid;
        else l=mid+1;
    }
    cout<<l;
    return 0;
}

[TJOI2007]路标设置

同数列分段

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int a[N];
int n,k,L;

bool check(int x)
{
    int num=0;
    for(int i=1;i<n;i++)
    {
        if(a[i]-a[i-1]>=x)
        {
            num+=(a[i]-a[i-1])/x;   //如果原有路标的距离大于mid,计算需要增加的路标的个数
            if((a[i]-a[i-1])%x==0)num--;   //如果原有路标的距离等于mid,则不需要增加
        }
    }
    return num<=k;   //增加的路标大于最多可增设的路标数量,说明距离大,需要变小
}

int main()
{
    cin>>L>>n>>k;
    for(int i=0;i<n;i++)cin>>a[i];
    sort(a,a+n);   //注意排序
    
    int l=0,r=L+1;
    while(l<r)
    {
        int mid=l+r>>1;
        if(check(mid)) r=mid;
        else l=mid+1;

    }
    cout<<l;
    
    return 0;
}

奶牛晒衣服

对弄干衣服的时间进行二分,mid作为自然干的时间,每一次二分都对应一个实际的烘干时间num,将mid和num比较进行区间更换

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=5*1e5+10;
int s[N];
int n,a,b;
bool check(int x)
{
    int m=0,num=0;
    for(int i=0;i<n;i++)
    {
        int k=0;
        m=s[i]-a*x;  //不进烘干机 也能减轻a*mid的湿度

        if(m<=0) continue;  //已经干了
        else
        {
            k=m/b;  //没干k为烘干时间
            if(m%b!=0)k++;  //小数时

            num+=k;   //num为需要的总的烘干时间
        }
    }
    return num<=x;  //mid大于实际烘干时间,mid取大了
}
int main()
{
    cin>>n>>a>>b;
    for(int i=0;i<n;i++) cin>>s[i];
    int l=0,r=N;
    while(l<r)
    {
        int mid=l+r>>1;
        if(check(mid))r=mid;
        else l=mid+1;
    }
    cout<<l;
    return 0;
}

kotori的设备

同奶牛晒衣服

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1*1e5+10;
double a[N],b[N];
int n;
double q,s;
bool check(double x)
{
    double m=0,num=0;
    for(int i=0;i<n;i++)
    {
        m=a[i]*x;

        if(m<=b[i]) continue;  //若设备已有的能量大于使用时间需要的能量,忽略该设备
        else
        {

            num+=(m-b[i]);  //否则用充电器充电,使设备已有的能量等于使用时间需要的能量,并记录需要的能量。
        }
    }
    return num<=q*x;  //最后比较需要的能量总和和充电器最多提供的能量。
}
int main()
{
    cin>>n>>q;
    for(int i=0;i<n;i++) cin>>a[i]>>b[i],s+=a[i];
    if(s<=q)
    {
        cout<<"-1";  //若所有设备的消耗能量速度总和还是小于充电器的充电速度,输出-1。
        return 0; //必须return 0
    }

    double l=0,r=1e10;
    while(r-l>1e-6)  //注意不能是1e-8
    {
        double mid=(l+r)/2;
        if(check(mid))l=mid;
        else r=mid;
    }
    cout<<l;
    return 0;
}

卡牌

分两种情况讨论:

1.空白牌足够多,n种牌中原有牌的数量加上能手写牌的数量的和(c[i]=a[i]+b[i])最小值(c[i])是在不全手写的套牌数的最小值,总的空白牌减去用过的除以n,是额外加的套牌数

2.空白牌不足够多或最多手写的张数足够多,二分套牌数,每次都计算一下需要的空白牌,最后判断空白牌是否大于等于0,即够用

代码:

#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N=2*1e5+10;
LL n,m;
LL a[N],b[N],c[N];

bool check(int x)
{
    LL k=m;
        for(int i=0;i<n;i++)
        {
            if(x<a[i])continue;
            else if(x-a[i]<=b[i])
                k-=(x-a[i]);
            else if(x-a[i]>b[i])return false;
        }
    //cout<<"k="<<k<<endl;
    return k>0;
}
int main()
{
    cin>>n>>m;
    for(int i=0;i<n;i++)cin>>a[i];

    for(int i=0;i<n;i++)cin>>b[i],c[i]=b[i];
    LL l=*min_element(a,a+n),r=*max_element(a,a+n)+m/n;

    LL s=*max_element(a,a+n),y=m;
    for(int i=0;i<n;i++)
    {
        if(a[i]<s)
        {
            if(s-a[i]<=c[i])
            {
                y-=(s-a[i]);
                c[i]-=(s-a[i]);
            }
        }
        if(y<0||c[i]<0)break;

    }
    int t=*min_element(c,c+n);
    if(y>=0&&t>=0)
    {
        s+=y/n;
        cout<<s;
        return 0;
    }

    while(l<r)
    {
        LL mid=(l+r+1)>>1;
        if(check(mid)) l=mid;
        else r=mid-1;
    }
    cout<<l;
    return 0;
}

放书

二分选择的书的数量mid,每次二分都计算一下当前情况下空位能够放下的书的数量num,在与选择的书的数量比较,进行区间更新

代码:


#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
LL a,b,n,m,h;

bool check(LL x)
{ 
    LL num=(n+m-x)/b*(h-b)+(n/b)*(b-a);  //空位能够放下的书的数
    return num>=x;
}
int main()
{
    int t;
    cin>>t;
    while(t--)
    {
        cin>>a>>b>>n>>m>>h;

        LL l=0,r=m-1;
        while(l<r)
        {
            LL mid=l+r+1>>1;
            if(check(mid)) l=mid;
            else r=mid-1;
        }
        cout<<n+m-l<<endl;
    }
    return 0;
}

Chat Ban

将小A发送的信息条数分为三种情况:

  1. 小A能够发送的信息条数 <=n 条

  2. 小A能够发送的信息条数 <=n条且<=2n-1 条

  3. 小A能够成功发送整个符号三角形

依据上面的分类得出:

  • 对于第一种情况,直接查找小A能够发送的信息条数 x ,此时 x 满足 f(n-1)<x<= f(n)。

  • 对于第二种情况,反面考虑,查找管理员在他还差 y 条信息没发送时将他禁言,此时 yy满足 f(n-1)-1<=y<f(n)-1。

  • 对于第三种情况,直接输出2n−1 即可。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
LL k,x;
int main()
{
    LL t;
    cin>>t;
    while(t--)
    {

        cin>>k>>x;   //k表示发消息的个数,x表示最多发送的符号
        if(((1+k)*k/2+(1+k-1)*(k-1)/2)<x)cout<<2*k-1<<endl;   //消息全部发出的符号总数小于x时

        else
        {
            LL l=0,r=2*k;   //二分
            while(l<r)
            {
                LL mid=l+r>>1;
                LL s=0,ss=0;  //s表示能发出mid个消息的总符号,ss表示能发出mid-1个消息的总符号,ss<x<=s才符合条件

                if(mid<=k)s=((1+mid)*mid)/2,ss=(mid*(mid-1))/2;
                else s=((1+k)*k)/2+(mid-k)*(k-1+k-(mid-k))/2,ss=((1+k)*k)/2+(mid-k-1)*(k-1+k-(mid-1-k))/2;

                if(s>=x) r=mid;   //mid个消息的总符号大于x时
                else if(ss<x)l=mid+1;     //mid-1个消息的总符号小于x时
                else         //ss<x<=s才符合条件
                {
                    l=mid;
                    break;
                }
            }
            cout<<l<<endl;
        }
    }
    return 0;
}

Binary String

思路参看:(282条消息) CF1680 C. Binary String(贪心)_樱落二瓣七里香的博客-CSDN博客

代码:

#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+10;
string str;
int s[N];
int main()
{
    int T;
    cin>>T;
    while(T -- )
    {
        cin>>str;
        int n = str.size(), cnt = count(str.begin(), str.end(), '1');  //cnt为字符串中1的个数S1
        str = " "+str;
        for(int i = 1; i<=n; i++)
            s[i] = s[i-1] + (str[i] == '0');   //s[]为前n个子串中0的个数
        int res = cnt;
        for(int i = cnt; i<=n; i++)
        {
            res = min(res, s[i] - s[i-cnt]);    //i和i-cnt为长度是cnt的子串,求该子串中0的个数的最小值
        }
        cout<<res<<endl;
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值