【蓝桥杯第二天---二分法】

二分

确定一个区间,使得目标值一定在区间内
找一个性质,使其满足:

  • 性质具有二段性(一半满足,一半不满足,中间无缝隙)
  • 答案是二段性的分界点
整数二分

退出条件:区间中只有一个数时
答案有两种情况,红色区间的右端点,绿色区间的左端点
在这里插入图片描述

1. 红色区间的右端点

将 [L , R] 分为 [L, M-1] , [ M , R]
if M在红色区域,则 证明 ans 在[ M, R ] 内
else 说明 ans 仍在 [ L, M -1]

  1. 绿色区域的左端点

将 [L , R] 分为 [L, M] , [ M+1, R]
if M在绿色区域,则 证明 ans 在[ L,M ] 内
else 说明 ans 仍在 [ M+1, R]

整数二分的步骤

  1. 找一个区间[L,R],使得答案一定在其中
  2. 找一个判断条件,使得该判断条件一定具有二段性,并且答案一定在该二段性的分界点
  3. 分析中点Mid在该判断条件下是否成立,如果成立,考虑答案在哪个区间;如果不成立,考虑答案在哪个区间
  4. 如果更新方式写的是R=Mid,无需调整,如果答案写的是L=Mid,则中点需要调整成为Mid = (L+R+1)/2
浮点数二分

将区间 [ L , R ] 划分为 [ L , M ] , [ M , R ]
退出条件:区间长度小于某个数时;如while( R-L > 1e-6 )
if ans 在[ L,M ] 内 ,R = M;
else 说明 ans 在 [ M, R]内,L=R

Q1 数的范围

题目描述:

给定一个升序排列的数组,长度为n,再给出q个查询,每个查询包含一个元素k,返回该元素在数组中的其实位置和终止位置(0开始计数),如果不存在则返回 -1 -1

思路:

  • 起始位置:
  1. 取区间L = 0 , R= n-1; Mid =L +R>>1(等价于除2,+的优先级比>>优先级高)
  2. 我们考虑当 a[Mid] < k 时,k更大,应该在mid的右边且一定不会是a[mid],所以答案一定是在 [Mid+1, R],所以 此时令L = Mid+1
  3. 反之R = Mid
  4. 由于是R = Mid ,所以不做任何处理
  • 终止位置
  1. 取区间L = 0 , R= n-1; Mid =L +R>>1(等价于除2,+的优先级比>>优先级高)
  2. 我们考虑当 a[Mid] > k 时,k更小,应该在mid的左边且一定不会是a[mid] ,所以答案一定是在 [L, Mid-1],所以 此时令R = Mid-1
  3. 反之L = Mid
  4. 由于是L= Mid ,所以 Mid = L + R + 1 >> 1

代码:

#include<iostream>
#include<algorithm>

using namespace std;

const int N = 100010;

int n , q , k;

int a[N];

int main()
{
    cin >> n  >> q;
    for(int i = 0 ; i< n ; i++)cin >> a[i];
    
    while( q-- )
    {
        int k ;
        scanf("%d",&k);
        int l = 0, r = n-1;
        //这里是找起始位置
        while( l < r)
        {
            int mid = (l + r )/2;
            if(a[mid] < k)l = mid+1;
            else r = mid; 
        }
        if(a[l] == k)
        {
            printf("%d ", l);
            //这里是找终止位置
            r = n-1;
            while( l < r)
            {
                //因为是 l = mid,所以mid 需要上取整
                int mid = (l + r + 1)/2;
                if(a[mid] > k)r = mid-1;
                else l = mid;
            }
            printf("%d ", l);
        }else printf("-1 -1");
       
        puts("");
    }
    return 0;
}

Q2 数的三次方根

题目描述:

给定一个浮点数n,求他的三次方根 也即 n 3 \sqrt[3]{n} 3n

思路:

本题属于浮点数二分,我们二分查找接近的数,当查找区间长度小于某一特定值时,我们就认定找到了答案
如 R-L < 1e-8时就得到正确答案。
这题直接看代码,比较简单。

代码:

#include<iostream>
#include <cstdio>
using namespace std;

int main()
{
    double n ;
    cin >> n;
    
    double l = -100 , r = 100;
    
    while( r-l > 1e-8)
    {
        double mid = (l+r)/2;
        if(n > mid*mid*mid)l=mid;
        else r = mid;
    }
    printf("%lf",l);//正常的%lf就是默认保留六位小数,如有其他需求就%.nlf的形式来保留n位小数
    return 0;
}

Q3 机器人跳跃问题

题目描述:

机器人再0位置,初始能量为e0, 然后有1~n ,n个不同高度的柱子,机器人每一步跳到下一个柱子上,如果h[k+1] >e 那么机器人的能量E将会减少 h[k+1] -e ,否则将会增加 e - h[k+1] ,
所以 e = e-(h[k+1] -e) = e * 2 - h[k+1]; 要求过程中e不可以为负数
问最低且满足要求的e0是多少

思路:

首先假设答案为 e0 , 我们可以知道所有大于e0的都满足要求,所有小于e0的都不满足要求;
所以我们就二分解空间0-1e5(数据范围是0~1e5)
如果check(Mid) 满足条件,就代表答案在 [L , Mid ] 即令 R = Mid
否则就令 L = Mid +1
check函数就是用来检验该初始值是否满足要求,在check中,如果e>1e5则代表一定是解,直接返回true即可;

代码:

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 100010;

int n ;
int h[N];

bool check(int e)
{
    for(int i = 1 ; i <= n ; i ++)
    {
        e = e * 2 - h[i];
        // 如果当前能量e已经超过最大的高度,那么一定满足要求,所以直接返回true
        if(e > 1e5) return true;
        // 如果过程中能量e小于0了则代表不满足要求,返回false
        if(e < 0) return false;
    }
    return true;
}

int main()
{
    int r= 1e5;
    
    int l =0;
    cin >> n ;
    for(int i = 1 ; i <= n ; i ++) cin >> h[i];
    
    while( l < r)
    {
        int mid = l + r >> 1;
        // 满足则答案一定在 l ~ mid中,否则答案在  mid+1 ~ r 中
        if(check(mid)) r = mid;
        else l = mid+1;
    
    cout << l<<endl;
    return 0;
    
}

Q4 四平方和

题目描述:

给定正整数n, 将其拆分成 n = a 2 a^2 a2 + b 2 b^2 b2+ c 2 c^2 c2 + d 2 d^2 d2的形式,一定会有解,并对所有的可能表示法按 a,b,c,d为联合主键升序排列,最后输出第一个表示法。

思路:

  • 暴力解:我们可以直接枚举a ,b ,c; d 2 d^2 d2 = n - a 2 a^2 a2 - b 2 b^2 b2- c 2 c^2 c2 ; 这样就可以获取到满足要求的解
  • 二分
  1. 我们分别枚举c,d 和 a,b
  2. 首先枚举c、d,并记录下来所有的s = c 2 c^2 c2 + d 2 d^2 d2,我们使用结构体的方式进行记录,并且要重写小于号,因为要按照s c d的顺序进行排序。
  3. 然后枚举 a 和 b,计算 t = n - a 2 a^2 a2 - b 2 b^2 b2,然后二分记录的s,进行t 和s 的比对,然后选择出对应的c和d,输出答案即可

代码:

暴力解:( O ( N 3 ) O(N^3) O(N3))

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
int n;

int main()
{
    cin >> n ;
    
    for(int a = 0 ; a*a <= n; a++)
        for(int b = a ; a*a + b * b <= n ; b++ )
            for(int c = b ; a * a + b* b + c * c <= n ; c++)
            {
                int t = n - a*a - b *b - c *c;
                int d = sqrt(t);
                if(d*d == t)
                {
                    printf("%d %d %d %d", a ,b,c,d);
                    return 0;
                }
            }
    
    return 0;
}

二分法( O ( N 2 l o g N ) O(N^2logN) O(N2logN)

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
int n , m;
const int N = 2500010;

// 记录c和d以及s
struct Sum
{
    int s , c, d;
	//重写小于号,便于排序,这种写法记住就好,很实用也很常用
    bool operator<(const Sum &t)const
    {
        //如果s不同,s小的在前
        if(s != t.s) return s < t.s;
        //如果s相同,c不同,按照c小的在前
        if(c != t.c) return c < t.c;
        //如果 s 和 c 都相同,按照d小的在前,(通常d也就确定了,其实不必要 直接return d;也是对的)
        return d < t.d;
    }
}sum[N];

int main()
{
    cin >> n ;
    
    for(int c = 0 ; c * c <= n ; c ++)
        for(int d = c ; c * c + d * d <= n ; d++)
            sum[m++] = {c*c + d * d,c ,d };
    
    sort(sum,sum+m);
    for(int a = 0 ; a*a <= n; a++)
        for(int b = a ; a*a + b * b <= n ; b++ )
        {
            int t = n - a * a - b * b;
            int l = 0 , r = m;
            while( l < r)
            {
                int mid = l + r >> 1;
                if( sum[mid].s >= t) r = mid;
                else l = mid + 1;
            }
            if( sum[l].s == t)
            {
                printf("%d %d %d %d\n",a,b,sum[l].c,sum[l].d);
                return 0;
            }
        }
    
    return 0;
}

Q5 分巧克力

题目描述:

有n块巧克力,第i块的巧克力高为h[i],宽为 w[i]。
要求,将这些巧克力切成k块,每一块都是正方形,且大小相同。
求 满足要求的前提下,最大的正方形长度是多少。

思路:

我们二分所有可能的长度x , 然后使用 (h[i] /x) *(w[i] /x) 来计算每一块巧克力可以切的块数
然后将所有块数加起来,和k作比较,如果大于等于则代表满足要求。
如果check(mid) 满足,则代表答案在[mid , R] 中, 则 l = mid
否则 r = mid -1;

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;

const int N = 100010;

int n , k;
int h[N] , w[N];

bool check(int x)
{
    int res = 0 ;
    for(int i = 0 ; i < n ; i ++)
    {
        res += (h[i] / x) * (w[i] / x);
    }
    if(res >=k) return true;
    return false;
}

int main()
{
    cin >> n >> k ;
    for(int i = 0 ; i < n ; i ++)cin >> h[i] >> w[i];
    
    int l = 1 , r = 1e5;
    
    while( l < r )
    {
        int mid = l + r + 1  >> 1;
        //越大越好,所以如果当前mid满足,即继续往右边二分,选更大的
        if( check(mid) ) l = mid;
        else r = mid - 1;
    }
    cout << l << endl;
    
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值