蓝桥杯高频考点——二分(含C++源码)

基本框架

二分查找的思维确实非常简单 就是引入一个猜炸弹编号的问题 相信大家玩过对吧 假如炸弹是0-100中间的一个数字 不会玩的人可能一个一个猜 会玩的人一般上来就猜50 25 …

二分查找代码中的细节很重要但是真正的坑根本就不是那个细节问题,而是在于到底要给 mid 加一还是减一,while 里到底用 <= 还是 <,

这就要分别对应两种写法 :
一种是左闭右闭区间 一种是左闭右开区间 讲解视频我附在这里:

代码随想录的详细讲解

labuladong 算法笔记 :

在这里插入图片描述

整数查找(序列二分的模版题 建议先做)

在这里插入图片描述
题目链接

满分代码及思路

首先题目其实说的非常直白就是根据给的flag(1 2 3 4 )分别对应四种不同的查询方案 那么我们的main函数就分别对于这四种情况 分别调用函数返回结果即可

做完你再看 序列二分的本质上 是不是 在有限的一个区间或者序列中 快速找到满足条件的数

solution

#include <iostream>
using namespace std;
int n, q;
const int N = 1e5 + 9;
int a[N]; // 存一下序列A
int flag; // 取决于我们该怎么去查找
int l, r, x;
int ans;

// 输出 A[l∼r] 中等于 x 最左边的数的下标,若不存在输出 -1
void search1(int l, int r, int x) {
    ans = -1;
    while (l <= r) {
        int mid = l + (r - l) / 2;
        if (a[mid] == x) {
            ans = mid;
            r = mid - 1;
        } else if (a[mid] < x) {
            l = mid + 1;
        } else {
            r = mid - 1;
        }
    }
}

// 输出 A[l∼r] 中等于 x 最右边的数的下标,若不存在输出 -1
void search2(int l, int r, int x) {
    ans = -1;
    while (l <= r) {
        int mid = l + (r - l) / 2;
        if (a[mid] == x) {
            ans = mid;
            l = mid + 1;
        } else if (a[mid] < x) {
            l = mid + 1;
        } else {
            r = mid - 1;
        }
    }
}

// 输出 A[l∼r] 中大于等于 x 的第一个数的下标,若不存在输出 -1
void search3(int l, int r, int x) {
    ans = -1;
    while (l <= r) {
        int mid = l + (r - l) / 2;
        if (a[mid] >= x) {
            ans = mid;
            r = mid - 1;
        } else {
            l = mid + 1;
        }
    }
}

// 输出 A[l∼r] 中大于 x 的第一个数的下标,若不存在输出 -1
void search4(int l, int r, int x) {
    ans = -1;
    while (l <= r) {
        int mid = l + (r - l) / 2;
        if (a[mid] > x) {
            ans = mid;
            r = mid - 1;
        } else {
            l = mid + 1;
        }
    }
}

int main() {
    cin >> n >> q;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    while (q--) {
        cin >> flag >> l >> r >> x;
        if (flag == 1) {
            search1(l , r , x);
        } else if (flag == 2) {
            search2(l , r , x);
        } else if (flag == 3) {
            search3(l , r , x);
        } else if (flag == 4) {
            search4(l , r , x);
        }
        cout << ans << endl;
    }
    return 0;
}    

子串简写

在这里插入图片描述
题目链接

满分代码及思路

solution 1(暴力 模拟双指针70分)

对于这道题目其实我首先想到的就是双指针,具体拿这题来说就是,我们现在有两个指针left right 现在要做的就是 在给定的字符串中枚举出所有同时满足

1.长度大于等于K;
2.以c1开头以c2结尾

以上两个条件的所有子串 对吧
那么就很好办了呀 我们先让left指针找到c1,然后通过不断移动right
枚举出所有以left此时位置为左端点 且满足条件的所有合法情况
每次更新答案count 就好 最后输出一下
但是有几个比较大的样例超时了 这时候我们就需要想办法优化一下:
在这里插入图片描述
因为我们这个双指针的方法 主要依靠两个for循环 时间复杂度是O(n*n)

Q:所以我们要思考的是我们可以去优化的点在哪啊?

也就是说这个代码有什么重复且不必要的操作吗?

我觉得这个思考很重要很重要

A:二分相对于双指针优化的点是不是在于c2位置的确定啊 因为双指针需要枚举right每一种情况吧 因为我们就像一个瞎子 我们的right只有走到它的儿子面前才能跟他相认

具体来说,在双指针方法里,对于每一个以 c1 开头的位置,代码需要从 left + K - 1 开始,逐个枚举所有可能的 right 位置,直到找到以 c2 结尾的位置,从而判断是否满足子串长度大于等于 K 的条件

二分查找方法首先记录下所有 c1 和 c2 出现的位置。对于每一个 c1 出现的位置,要寻找满足子串长度大于等于 K 的 c2 位置时,它利用 c2 位置数组的有序性(因为位置是按顺序记录的)进行二分查找
此时我们的right指针他是一个视力很好的人 他知道自己的子女 在这条路的哪个方位

二分查找每次将搜索范围缩小一半,其时间复杂度为 (O(log m)),其中 m 是 c2 出现的次数。对于所有 c1 出现的位置都进行二分查找,整体时间复杂度就是 (O(n log m)),在一般情况下可以近似看作 (O(n log n)),相比于双指针方法的 (O(n^2)) 有了显著的优化。

#include <iostream>
#include <string>

// 引入标准库命名空间
using namespace std;

// 双指针方法
int double_point(int K, const string& S, char c1, char c2) {
    int n = S.length();
    int count = 0;
    for (int left = 0; left < n; ++left) {
        if (S[left] == c1) {
            for (int right = left + K - 1; right < n; ++right) {
                if (S[right] == c2) {
                    ++count;
                }
            }
        }
    }
    return count;
}

int main() {
    int K;
    string S;
    char c1, char c2;

    // 读取输入
    cin >> K;
    cin >> S >> c1 >> c2;

    // 计算结果
    int result = double_point(K, S, c1, c2);

    // 输出结果
    cout << "双指针方法结果: " << result << endl;

    return 0;
}
    

solution 2(二分 AC)

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

using namespace std;

// 二分查找方法
int binary_search(int K, const string& S, char c1, char c2) {
    int n = S.length();
    vector<int> start;
    vector<int> end;

    // 记录 c1 和 c2 出现的位置
    for (int i = 0; i < n; ++i) {
        if (S[i] == c1) {
            start.push_back(i);
        }
        if (S[i] == c2) {
            end.push_back(i);
        }
    }

    int count = 0;
    for (int s : start) {
        // 二分查找满足条件的 c2 位置
        int left = 0, right = end.size() - 1;
        int target = s + K - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (end[mid] >= target) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        count += end.size() - left;
    }
    return count;
}

int main() {
    int K;
    string S;
    char c1, c2;

    // 读取输入
    cin >> K;
    cin >> S >> c1 >> c2;

    // 计算结果
    int result = binary_search(K, S, c1, c2);

    // 输出结果
    cout << "二分查找方法结果: " << result << endl;

    return 0;
}    

管道

在这里插入图片描述
题目链接

满分代码及思路

代码和思路参考:视频讲解
BZW :这是我偶然刷到的一个up 真的很优秀很有思维

样例解释与思路分析

对于这道题目首先我们需要搞清楚 我们要干嘛
可以将这个管道看成一条线段 阀门就是线段上的点
我们需要明确的是这个阀门只要一打开就会向左右两边同时灌水 就可以看成是以这个点为中心向左右两边延伸 length的长度
并且 len与时间t的函数是一个单调递增函数

在这里插入图片描述

solution

#include <iostream>
#include <bits/stdc++.h>
using namespace std;
int n,len;
const int N=1e5+10;
int a[N][2];//存储输入每个阀门的位置和时间
struct door{
  int L;//位置
  int S;//时刻
}d[N];
struct line{
  int l;
  int r;
}w[N];
//sort比较函数 从小到大排左端点 如果一样就把右端点大的放后面
bool cmp(line A,line B)
{
  if(A.l!=B.l)
  {
     return A.l<B.l;
  }else{
    return A.r<B.r;
  }

}
bool check(int x)
{
  int cnt=0;
  for(int i=0;i<n;i++)//生成区间
  {
    if(x<d[i].S)continue;
    w[cnt].l=d[i].L-(x-d[i].S);
    w[cnt].r=d[i].L+(x-d[i].S);
    cnt++;
  }
  sort(w,w+cnt,cmp);
  int last=0;
  //线段合并
  for(int i=0;i<cnt;i++)
  {
    //为什么是last+1 因为题目把这些管道看作是一个个离散的点 所以只要我们新区间的右端点>last+1(l=临界情况)
    //就return false
    if(w[i].l>last+1)
    {
      return false;
    }
    last=max(w[i].r,last);
  }
  if(last<len)
  {
    return false;
  }
  return true;
}
int main()
{
  cin>>n>>len;
  for(int i=0;i<n;i++)
  {
    cin>>d[i].L>>d[i].S;
  }//读入完成
  int mid=0;
  int l=0;
  int r=2e9;//最坏的情况就是从1ee9开始流 直到2e9才能铺满管道
  //二分答案 左闭右开写法
  while(l<r)
  {
    mid=(l+r)/2;
    if(check(mid))//如果mid这个时间能铺满整个管道 说明在mid右边的时刻都是合法的
    //那么我们就需要操作:
    //不减1是因为我们不能保证mid是不是临界点 也就是我们不知道mid-1是什么情况
    {
      r=mid;
    }else{
      l=mid+1;
    }
  }
  cout<<l<<endl;
  return 0;
}

最大通过数

在这里插入图片描述
题目链接

满分代码及思路

solution 1(贪心 50分)

// 我们现在需要做的就是用这K个水晶 通过尽可能多的关卡 并且我们不需要关心水晶的分配问题
// 也就是说两个人共享一个背包 首先在闯关之前 我肯定想要比较一下左右两个入口 
// 优先通过消耗水晶小的关卡 这样才能保证 最好通过关卡的数量最多
#include <iostream>
using namespace std;
const int N = 2e5 + 9;
int a[N], b[N]; // 分别表示左右两边关卡的能源数量
int n, m, k;    // 初始状态下有n+m道关卡 一共有K个水晶
int passCount;

void work() {
    int i = 0, j = 0;
    // 先处理左右两边都有的关卡
    while (i < n && j < m) {
        if (a[i] <= b[j] && k >= a[i]) {
            passCount++;
            k -= a[i];
            i++;
        } else if (k >= b[j]) {
            passCount++;
            k -= b[j];
            j++;
        } else {
            break;
        }
    }
    // 处理左边剩余的关卡
    while (i < n && k >= a[i]) {
        passCount++;
        k -= a[i];
        i++;
    }
    // 处理右边剩余的关卡
    while (j < m && k >= b[j]) {
        passCount++;
        k -= b[j];
        j++;
    }
}

int main() {
    cin >> n >> m >> k;
    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }
    for (int j = 0; j < m; j++) {
        cin >> b[j];
    }
    work();
    cout << passCount;
    return 0;
}

问题

在这里插入图片描述

solution 2(二分 AC)

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

const int N = 2e5 + 4;
ll a[N], b[N], pre_a[N], pre_b[N];
ll n, m, k;

// 检查是否可以通过 mid 个关卡
bool check(ll mid) {
    bool ok = false;  // 记录 mid 关卡数是否有可行的方案,不关心 a,b 各通过几关

    // 从关卡数少的人开始枚举(mid > max(m,n) 时)
    for (ll i = 0; i <= min(min(m, n), mid); ++i) {
        // 保证另外一个人有足够的关卡数来凑为 mid 关,没有就跳过
        if (mid - i > max(m, n)) continue;

        // 从关卡数少的人开始枚举(保证不越界)
        if (m <= n && pre_b[i] + pre_a[mid - i] <= k) {
            ok = true;
        } else if (n < m && pre_a[i] + pre_b[mid - i] <= k) {
            ok = true;
        }
    }
    return ok;
}

// 解决问题的函数
void solve() {
    cin >> n >> m >> k;

    // 读取左边入口每个关卡所需的宝石数
    for (int i = 1; i <= n; ++i) {
        cin >> a[i];
    }
    // 读取右边入口每个关卡所需的宝石数
    for (int i = 1; i <= m; ++i) {
        cin >> b[i];
    }

    // 计算 a[i] 前缀和,代表 a 通过 i 关所需的宝石
    for (int i = 1; i <= n; ++i) {
        pre_a[i] = pre_a[i - 1] + a[i];
    }
    // 计算 b[i] 前缀和,代表 b 通过 i 关所需的宝石
    for (int i = 1; i <= m; ++i) {
        pre_b[i] = pre_b[i - 1] + b[i];
    }

    // 二分查找最大通过的关卡数
    ll l = 0, r = m + n + 10;
    while (l + 1 != r) {
        ll mid = (l + r) >> 1;
        if (check(mid)) {
            l = mid;  // mid 可行,说明 mid 可能可以更大,更新 l
        } else {
            r = mid;
        }
    }
    cout << l;  // l 为答案
}

int main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t = 1;
    // cin >> t;
    while (t--) {
        solve();
    }
    return 0;
}
<think>嗯,用户想了解蓝桥杯中的差分算法。首先,我得先回忆一下差分的基本概念和应用场景。差分主要用于处理数组区间修改的问题,比如多次对数组的某个区间进行加减操作,最后再查询结果。这种方法可以大幅降低时间复杂度,从O(n)降到O(1)的每次操作,最后再通过前缀和恢复数组。 接下来,我需要考虑蓝桥杯比赛中常见的差分题型。通常这类题目会涉及到多次区间修改,比如给某个区间内的所有元素加上一个值,然后询问最终数组的状态或者某些特征值。例如,可能有一道题是给一个数组,进行m次操作,每次操作对区间[l, r]加k,最后求数组的最大值或者某个位置的数值。这时候使用差分就能高效解决。 然后,我需要整理差分的基本步骤。差分数组的构建是通过原数组相邻元素的差来生成的。具体来说,差分数组d[i] = a[i] - a[i-1],其中a是原数组。这样,当需要对原数组的区间[l, r]加上k时,只需要对差分数组的d[l] += k,d[r+1] -= k即可。最后通过前缀和操作还原原数组。 可能还需要举一个具体的例子来演示这个过程。比如原数组是[1, 3, 5, 7, 9],对应的差分数组是[1, 2, 2, 2, 2]。假设要在区间[2,4](假设索引从1开始)加上3,那么差分数组的第二个元素加3,第五个元素减3,得到新的差分数组[1,5,2,2,-1]。然后通过前缀和得到原数组变为[1,6,8,10,9]。这样用户更容易理解差分的作用。 另外,需要注意边界条件,比如当r是数组最后一个元素时,r+1可能会超出数组范围,这时候可以忽略对d[r+1]的操作,或者在差分数组长度设为n+1来避免越界。这点在实现时需要特别注意,否则会导致错误。 还要考虑时间和空间复杂度分析。差分法的初始构建需要O(n)时间,每次区间操作是O(1),最后恢复原数组需要O(n)时间,总的时间复杂度是O(n + m),其中m是操作次数。这比暴力法的O(mn)要好很多,尤其是在大规模数据的情况下。 可能还需要提到二维差分,虽然用户没有问,但如果有余力的话可以简要说明,但用户的问题主要集中在差分,所以可能暂时不需要深入二维的情况。 另外,蓝桥杯中的题目可能会有变种,比如结合前缀和、贪心算法或者其他数据结构一起使用。需要提醒用户注意题目的具体要求,灵活应用差分技巧。例如,有些题目可能需要先进行多次区间修改,然后进行查询,这时候差分就能高效处理。 最后,给出一个例题和代码示例会更有帮助。比如,假设题目是给定一个长度为n的数组,初始全为0,进行m次操作,每次操作对区间[l, r]加k,最后输出最终的数组。这时候可以用Python代码演示如何实现差分数组的构建、修改和恢复过程。 总结一下,回答的结构应该是先介绍差分的基本概念,然后说明其应用场景,详细步骤,举例说明,注意点,以及例题和代码。这样用户能够逐步理解并应用差分算法解决蓝桥杯中的相关问题。</think>### 蓝桥杯差分算法详解 #### 1. **差分的基本概念** 差分是一种**数组区间高效修改**的技术,常用于处理多次区间增减操作后求最终数组的问题。其核心思想是通过**差分数组**将区间操作的时间复杂度从$O(n)$优化到$O(1)$。 #### 2. **差分的作用场景** - **典型问题**:给定数组$a$,进行$m$次操作,每次对区间$[l, r]$加上值$k$,最后求数组$a$的结果。 - **暴力法缺点**:直接遍历区间修改,时间复杂度为$O(mn)$,无法通过大规模数据。 - **差分优势**:通过预处理差分数组,将每次操作优化为$O(1)$,总时间复杂度$O(n + m)$。 --- #### 3. **差分数组的构建** - **定义**:差分数组$d$满足: $$d[i] = a[i] - a[i-1] \quad (i \geq 1)$$ $$d[0] = a[0] \quad (i=0)$$ - **逆向恢复**:对差分数组求前缀和即可还原原数组: $$a[i] = d[0] + d[1] + \dots + d[i]$$ --- #### 4. **区间修改的核心操作** 若要对原数组$a$的区间$[l, r]$加上$k$,只需修改差分数组的两处: 1. **起点**:$d[l] += k$(使得前缀和从$l$开始增加$k$) 2. **终点后一位**:$d[r+1] -= k$(抵消$r+1$之后的影响) --- #### 5. **操作示例** **原数组**:$a = [1, 3, 5, 7, 9]$,对应的差分数组$d = [1, 2, 2, 2, 2]$ **操作**:对区间$[2, 4]$(索引从1开始)加3 **修改差分数组**: - $d[2] += 3$ → $d = [1, 2+3, 2, 2, 2]$ - $d[5] -= 3$(若数组长度允许) → $d = [1, 5, 2, 2, -1]$ **恢复原数组**: $a' = [1, 1+5=6, 6+2=8, 8+2=10, 10+(-1)=9]$ --- #### 6. **边界处理技巧** - **数组越界**:若$r$是最后一个元素,无需操作$d[r+1]$(或定义差分数组长度为$n+1$)。 - **索引对齐**:注意题目中的区间是**从0还是1开始**,避免偏移错误。 --- #### 7. **代码实现(Python)** ```python n = 5 # 数组长度 m = 1 # 操作次数 a = [0] * (n + 2) # 差分数组(多开空间防越界) # 模拟操作:对区间[2,4]加3(假设索引从1开始) l, r, k = 2, 4, 3 a[l] += k a[r + 1] -= k # 求前缀和还原原数组 for i in range(1, n + 1): a[i] += a[i - 1] print("最终数组:", a[1:n+1]) # 输出: [0, 3, 3, 3, 0](若初始全0) ``` --- #### 8. **蓝桥杯真题应用** - **例题特点**:通常结合“区间修改+最终极值/总和”考查。 - **真题示例**: **问题**:给定长度为$n$的数组,初始全0,进行$m$次区间加操作,求最终数组的最大值。 **解法**:用差分记录所有操作后,求前缀和并遍历找最大值。 --- #### 9. **扩展:二维差分** 对于二维数组的区间修改(如矩阵块加减),可通过二维差分实现$O(1)$操作,但蓝桥杯较少涉及。 --- #### 总结 差分是蓝桥杯高频考点,需掌握**差分数组构建、区间修改公式、边界处理**。重点练习“多次操作后求数组状态”类题目。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值