蓝桥杯 第二讲 二分与前缀和

一、二分

1.整数二分

image
整数二分步骤:

  1. 找一个区间[L,R],使得答案一定在该区间中
  2. 找一个判断条件,使得该条件具有二段性,并且答案一定是该二段性的分界点
  3. 分析中点M在该判断条件下是否成立,如果成立,考虑答案在哪个区间;如果不成立,考虑答案在哪个区间;
  4. 如果更新方式写的是R = Mid,,则不用做任何处理;若更新方式写的是L = Mid,则需要在计算Mid上加1
    789. 数的范围
int find_l(int x)
{
    int l = 0, r = n-1;
    while(l < r)
    {
        int mid = l+r>>1;
        if(x <= a[mid]) r = mid;
        else l = mid + 1;
    }
    return l;
}
int find_r(int x)
{
    int l = 0,r = n-1;
    while(l < r)
    {
        int mid = l+r+1>>1;
        if(x >= a[mid]) l = mid;
        else r = mid - 1;
    }
    return l;
}

2.实数二分

实数较为稠密,对边界问题要求较低
image
790. 数的三次方根

#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;

double double_binary(double x)
{
    double l = -10000,r = 10000;
    while(r-l > 1e-8) //精度取较大些更保险
    {
        double mid = (l+r)/2;
        if(mid*mid*mid <= x)
        {
            l = mid;
        }
        else if(mid*mid*mid > x)
        {
            r = mid;
        }
    }
    return l;//精度差值符合范围,输出任意均可
}
int main()
{
    double x;
    cin>>x;
    printf("%.6lf",double_binary(x));
    return 0;
}

730. 机器人跳跃问题
image

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1e5+10;
int n,h[N],Maxh = -1;

bool check(int x)//x表示起始能量
{
    for(int i=1;i<=n;i++)
    {
        x = 2*x - h[i]; //根据题意得出的能量递推公式
        if(x<0) return false;//如果过程中出现能量小于0的情况,说明x小了
        if(x>=Maxh) return true;//如果过程中出现能量大于最大高度的情况,根据公式,x是递增的,说明一定是正数
    }
    return true;//没有出现小于0的情况,符合题意的x
}

int binary_search()//二分找到能完成任务的最小的初始能量值,用从左边找的模板
{
    int l = 0,r = 1e5;//r为最大能量
    while(l<r)
    {
        int mid = l+r>>1;
        if(check(mid)) r = mid;//如果mid成立,说明可能还有比mid小的可成立的数,范围向左缩小一半
        else l = mid + 1;//不成立,范围向右缩一半
    }
    return l;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) 
    {
        scanf("%d",&h[i]);
        Maxh = max(Maxh,h[i]);//找出最大高度
    }
    int res = binary_search();
    cout<<res<<endl;
    return 0;
}

1221. 四平方和
image

#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
const int N = 1e7;
int n;
struct Sum
{
    int s;
    int c;
    int d;
    bool operator<(const struct Sum &t)const
    {
        if(s!=t.s) return s<t.s;
        if(c!=t.c) return c<t.c;
        return d<t.d;
    }
}sum[N];
int main()
{
    cin>>n;
    int cnt = 0;
    for(int c = 0;c*c <= n;c++)
    {
        for(int d = c;c*c + d*d <= n;d++)
        {
            sum[cnt++] = {c*c + d*d,c,d};
        }
    }
    sort(sum,sum+cnt);//按照sum,c,d关键字升序排序
    for(int a=0;a*a<=n;a++)//升序枚举a和b
    {
        for(int b=0;a*a+b*b<=n;b++)
        {
            int l=0,r = cnt-1;//从左边二分找到对应的平方和,使得c和d最小
            while(l<r)
            {
                int mid = l+r>>1;
                if(sum[mid].s >= n-a*a-b*b) r = mid;
                else l = mid + 1;
            }
            if(sum[l].s == n-a*a-b*b)
            {
                printf("%d %d %d %d\n",a,b,sum[l].c,sum[l].d);
                return 0;
            }
        }
    }
    return 0;
}

1227. 分巧克力

#include<iostream>
#include<cmath>
using namespace std;
const int N = 1e5+10;
int n,k;
int h[N],w[N];

bool check(int x)//x表示切除的正方形边长,x越大,分的个数越少,反之越大,
{
    int sum = 0;
    for(int i=0;i<n;i++)
    {
        sum += (h[i]/x)*(w[i]/x);//表示以边长x分得的个数,每个人边长都相等
        if(sum >= k) return true;
    }
    return false;
}
int main()
{
    scanf("%d%d",&n,&k);
    for(int i=0;i<n;i++)
    {
        scanf("%d%d",&h[i],&w[i]);
    }
    int l=1,r = 1e5;//输入保证每位小朋友至少能获得一块 1×1 的巧克力
    //x表示切除的正方形边长,x越大,分的个数越少,反之越大
    while(l<r)//要找到一个最大的边长x,使得分的块数满足k个,这就是二分思路的由来
    {
        int mid = l+r + 1>>1;//由于找最大,使用向右二分模板
        if(check(mid)) l = mid;//如果可以分k块,则向右找,增大边长,减少块数,寻找更大的满足题意的边长
        else r = mid - 1;//否则减少边长,增大块数
    }
    cout<<l<<endl;
    return 0;
}

二、前缀和

1230. K倍区间
image
解题思路:

  • 首先,对于这种求一个数列某一段和的问题,显然用到前缀和的处理方法
  • 第二个需要解决的问题就是如何确定某一段数能不能被K整除,能够被K整除,意味着余数为0,所以一段数与K取模得0就说明这段数是要找的K倍区间
  • 可以从前往后枚举,设当前枚举到第位,且它的前缀和sum[i]与K取模为m,如果在之前的第jj位前缀和同样是与K取模得m的话,sum[i]−sum[j]sum[i]−sum[j]也就是Aj+1…Ai这段数与K取模为00,这一段就可以计入答案了,对于任意一个,只需要统计前面有多少个这样的jj即可
  • 具体的做法就是开一个计数数组cnt[],cnt[i]就是计算余数为i的前缀和有多少个
  • 另外要注意一点是,假如某一段数A1…AiA1…Ai,不需要减去区间,它本身就是一个K倍区间,但cnt[0]cnt[0]保存的是之前余数为00的前缀和的个数,这个区间本身没有被算进去,所以初始状态下,cnt[0]cnt[0]应该赋值为1
posted @   安河桥北i  阅读(51)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示