二分(二分搜索(查找序列中是否有x,查找序列中>=x的最小值,查找序列中>x的最小值,查找序列中<=x的最大值,序列中找最短子序列>=x,牛客小黑月赛24 B组队,2021年蓝桥杯B组F题),性质)

目录

二分

二分搜索

查找序列中是否有x

做法(解题思路):

查找序列中>=x的最小值

解题思路:

性质:

查找序列中>x的最小值

解题思路:

查找序列中<=x的最大值

解题思路:

序列中找最短子序列>=x

​​解题思路:

牛客小白月赛24 B组队

解题思路:

求>x的平方数

解题思路:

求sqrt(x)的精确值

解题思路:

2021年蓝桥杯B组F题

解题思路:


二分

二分搜索

查找序列中是否有x

给定一个有单调性的长度为n的序列,问你里面是否存在某个数x.本质是一种分治的思想

正常暴力循环遍历来做这道题,复杂度是O(n)

用二分搜索,复杂度是O(log n)

做法(解题思路):

对于有单调性的序列,我们每次检查序列区间中间位置值y,根据y的大小来缩小区域,反复进行此过程直到找到解,或找到可能区域为空为止

递归写法

#include <iostream>

using namespace std;
int n;
int fi(int a[], int l, int r)
{
    //如果找到了就返回位置下标记
    if(a[(l+r)/2] == n)
        return (l+r)/2;
    //如果没有找到就压缩左区域或右区域
    if(a[(l+r)/2] > n)
        return fi(a, l, (l+r)/2-1);
    else
        return fi(a, (l+r)/2+1, r);
}

int main()
{
    int a[] = {1, 2, 3, 4, 5, 6, 7 , 8, 9, 10};
    cin >> n;
    cout << fi(a, 0, 9);
    return 0;
}

迭代器写法

#include <iostream>

using namespace std;

int main()
{
    int a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
    int n, l = 0, r = 9, mid;
    cin >> n;
    //循环条件,左端点要小于等于有端点
    while(l <= r)
    {
        //除以2
        mid = (l + r) >> 1;
        if(a[mid] == n)
        {
            cout << "YES";
            return 0;
        }
        //判断大小,移动左右端点
        if(a[mid] > n)r = mid - 1;
        else l = mid + 1;
    }
    if(l > r)cout << "NO";
    return 0;
}

查找序列中>=x的最小值

给定一个有单调性的序列,问你大于等于x的最小值是多少(不存在输出No)

例如:{1,2,4,7,9} , x=4时应该得到4,x=5时应该得到7

解题思路:

 这道题是典型的二分后缀模型,所以直接套入模板,最终结束循环要判断 L 是否越界在进行输出

#include <iostream>

using namespace std;

int main()
{
    int a[] = {1, 2, 3, 4, 4, 6, 7, 8, 9};
    int n, l = 0, r = 9, mid;
    cin >> n;
    while(l <= r)
    {
        mid = (l + r) >> 1;
        if(a[mid] == n)
        {
            cout << n;
            return 0;
        }
        if(a[mid] > n)r = mid - 1;
        else l = mid + 1;
    }
    if(l >= 9)
    {
        cout << "NO";
    }
    else
        cout << a[l];
    return 0;
}

性质:

对于前缀模型,右端点 R 结束循环后一定在左区间的最右元素

对于后缀模型,左端点 L 结束循环后一定在右区间的最左元素

查找序列中>x的最小值

给定一个有单调性的序列,问你大于 x 的最小值是多少(不存在输出No)?

例如:{1,2,4,7,9} , x=4时应该得到7,x=5时应该得到7

解题思路:

题目求大于x的最小值,所以是后缀模型,最终输出 L 位置的值

#include <iostream>

using namespace std;

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

查找序列中<=x的最大值

给定一个有单调性的序列,问你小于等于x的最大值是多少

例如:{1,2,4,7,9} , x=4时应该得到4,x=3时应该得到2

解题思路:

求小于等于 x 的最大值,所以此题为前缀模型,最终输出 R 位置的值

#include <iostream>

using namespace std;

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

序列中找最短子序列>=x

给定一个正整数序列,让你取一个子段,使得其区间的和大于等于x,问你这个子段最短可能长度是多少。

例如:{1,2,4,7,9} , x=13时应该得到2,x=3时应该得到1

​​解题思路:

根据题目描述可以推出,我们的答案是求最短字段的长度,所以二分答案求的是长度,长度最少为1,最长为字段的总长度,而且当长度越长就越有可能满足>=x,所以此题为后缀模型,先预处理前缀和,在之后通过检查函数来判断是否成立,成立缩减右端点,不成立缩减左端点,在检查函数内,通过滑动窗口来判断这个窗口是否满足>=x,最后输出L即可(因为L会停留在后缀的最左元素)

#include <iostream>
using namespace std;
int a[1005], s[1005] = {0}, n, x;

int ok(int mid)
{
    int mx = 0;
    //i为答案的长度
    for(int i = mid; i <= n ; i++)
    {
        //i-mid 与 i 之间构成了长度为 mid 的滑动窗口
        //每次求最大值,原因是最大值是最有可能满足>=x的
        mx = max(mx, s[i] - s[i - mid]);
    }
    //最后传回判断
    return mx >= x;
}

int main()
{
    cin >> n >> x;
    //输入序列,预处理前缀和
    for(int i = 1; i <= n; i++)
    {
        cin >> a[i];
        s[i] = s[i-1] + a[i];
    }
    int l = 1, r = n, mid;
    while(l <= r)
    {
        mid = (l + r) >> 1;
        //cout << mid << " " << ok(mid) << endl;
        //用检查函数来判断更改哪个端点,如果判断函数传回满足,就缩减右端点
        //如果不满足就缩减左端点, 因为题目是给一个升序的序列,越往后越容易实现题目要求
        if(ok(mid))r = mid - 1;
        else l = mid + 1;
    }
    //此题为后缀模型,越往后越容易实现题目要求
    cout << l;
    return 0;
}

牛客小白月赛24 B组队

题目链接: 牛客小白月赛24 B组队


解题思路:

首先思考题目要我们求最大的人数,这个答案具有单调性的(人数越少越容易满足<=k的条件),所以这题是典型的二分答案前缀模型

首先将所有人的能力存入数组后从小到大进行排序,写一个二分答案前缀模型,然后判断函数内通过滑动窗口来判断是否满足<=k,如果满足就不需要再往后进行判断直接返回 真,否则判断完都没有返回 真,此时就代表不成立,返回 假,因为是前缀模型,最终输出 R

#include<bits/stdc++.h>
using namespace std;

#define ll long long
const int maxn = 2e5 + 5;
int n, k;
int a[maxn];

bool ok(int mid)
{
    //滑动窗口判断最大值减去最小值是否满足<=k,如果满足了就代表不需要判断后面的了,因为题目只求长度
    for(int i = 1; i + mid - 1 <= n; i++)
    {
        if(a[i + mid - 1] - a[i] <= k)
        {
            return true;
        }
    }
    return false;
}

int main()
{
    int t;
    cin >> t;
    while(t--)
    {
        cin >> n >> k;
        for(int i = 1; i <= n; i++)cin >> a[i];
        //将所有人的能力值从小到大排序
        sort(a + 1, a + n + 1);
        int l = 1, r = n;
        //二分答案前缀模型
        while(l <= r)
        {
            int mid = (l + r) >> 1;
            if(ok(mid))l = mid + 1;
            else r = mid - 1;
        }
        cout << r << endl;
    }
    return 0;
}

求>x的平方数

解题思路:

思考此题答案,明显具有单调性,当数字越大越能满足>x的条件,所以此题为二分答案后缀模型,最终输出 L

#include<bits/stdc++.h>
using namespace std;

#define ll long long
const int maxn = 1e9 + 5;
int x;

int ok(int mid)
{
    if(mid * mid > x)return 1;
    else return 0;
}

int main()
{
    cin >> x;
    int l = 0, r = sqrt(x);
    while(l <= r)
    {
        int mid = (l + r) >> 1;
        if(ok(mid))r = mid - 1;
        else l = mid + 1;
    }
    cout << l;
    return 0;
}

求sqrt(x)的精确值

解题思路:

首先思考此题答案是什么,答案就是x开根号后的值,越大越容易实现x的精确值,所以这是二分答案后缀模型,最终输出L

在不好确定循环次数的时候,不妨可以固定循环的次数T,只$T <= log n 即可

#include<bits/stdc++.h>
using namespace std;

#define ll long long
const int maxn = 1e9 + 5;
int x;

int main()
{
    cin >> x;
    double l = 0, r = 100;
    //因为我们只能取到近似值,很难碰巧遇到刚刚好的值
    for(int i = 0; i <= 50; i++)
    {
        double mid = (l + r) / 2.0;
        不+1 -1是因为,这种值通常都是小数点后很多位置
        if(mid * mid < x)l = mid;
        else r = mid;
    }
    cout << l;
    return 0;
}

2021年蓝桥杯B组F题

题目:蓝桥杯2021年第十二届国赛真题-123 - C语言网

解题思路:

根据题目要求我们可以知道

这题主要要会求前N项和的求和公式:(首项+末项)*项数/2

所以解题思路是,我们先将每一行看做一起的,做一个前缀和的预处理,比如2,就代表前两行的和,然后我们可以将一个下标L从1开始到本身的和,拆成他的上一行的前缀和+本行的几个元素的和,所以我们写一个函数sum1(z),去找出他的上一行,再加上它本身的几个元素并返回,由于题目是求字段和,所以我们L的位置的数也要加进来,最终就是sum1(r) - sum1(l - 1)

#include<bits/stdc++.h>
using namespace std;

#define ll long long
//通过公式算出结果是1580000多行
const ll maxn = 16e5;
ll sum[maxn];

//js()是首相加末项乘项数除二
ll js(ll i)
{
    return (1 + i) * i / 2;
}

ll sum1(ll z)
{
    //处理L等于1时,L-1为0
    if(z == 0)return 0;
    //同上maxn
    ll l = 1, r = maxn;
    while(l <= r)
    {
        ll mid = (l + r) >> 1;
        if(js(mid) < z)l = mid + 1;
        else r = mid - 1;
    }
    //因为上面循环保证了r是在z所处行的上一行,所以前缀和[R]要加上下一行的几个元素
    //z-r是总下标减去上面前缀和的标
    return sum[r] + js(z - js(r));
}

int main()
{
    //之所以用sacnf printf是因为数据很多,用cin cout很慢

    ll t, len = 0;
    //cin >> t;
    scanf("%lld",&t);
    //用len控制元素个数
    for(ll i = 1; len <= 1e12; i++)
    {
        //每一次加上一行
        len += i;
        //前缀和处理
        sum[i] = sum[i-1] + js(i);
    }
    while(t--)
    {
        ll l, r;
        //cin >> l >> r;
        scanf("%lld %lld",&l,&r);
        //L - 1是因为题目求从L-R的字段和
        //cout << sum1(r) - sum1(l - 1) << endl;
        printf("%lld\n",sum1(r) - sum1(l - 1) );
    }
    return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员shy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值