【蓝桥杯备赛Day4】基础算法

基础算法(概念、模板、练习)


一、枚举算法

枚举算法是一种通过穷举所有可能情况来解决问题的基础算法思想。它的基本思想是将问题的解空间 中的每个可能的解都枚举出来,并进行验证比较 ,找到满足问题条件的最优解或者所有解

适用场景

  1. 问题规模较小,解空间可被完整穷举。
  2. 不适合问题规模过大的场景,否则时间复杂度极高、效率低下。

注意

解空间:

解空间可以是一个范围内的所有数字(或二元组、字符串等数据),或者满足某个条件的所有数字。也可以是解空间树,一般可分为子集树和排列树,针对解空间树,需要使用回溯法进行枚举。通常使用循环去暴力枚举解空间。

循环枚举解空间:

根据问题需求确定枚举变量个数(如单变量用单层循环,二元组用双重循环或者用到STL)。

通过问题约束条件确定每个变量可能的取值范围,在循环体内进行条件验证(if...)、数值计算或结果输出操作

相关练习

1.特别数的和 - 蓝桥云课

2.反倍数 - 蓝桥云课

3.找到最多的数 - 蓝桥云课

4.好数 - 蓝桥云课

二、模拟

模拟算法通过模拟实际情况来解决问题,一般不涉及太难的算法,由较多的简单但是不好处理的部分组成的,考察细心程度和整体的逻辑思维。在解决模拟题时通常会编写多个辅助函数。例如 int 和 string 的相互转换、回文串的判断、日期的转换、各种特殊条件的判断等等。

相关练习

1.扫雷 - 蓝桥云课

注意

  1. 扫雷表格上下左右边界外没有网格,在遍历周围九宫格有无雷时,需要进行边界范围约束。

<1> _i_j 的含义

ij:当前要计算的位置(比如第 2 行第 2 列,记为 (2,2));

_i_j:遍历「当前位置周围九宫格」的位置。

<2> 理解循环中 i 从 max(1, i-1)min(n, i+1)遍历

这两个函数是为了避免越界(比如第一行的位置,没有 "上一行";最后一行的位置,没有 "下一行"),用具体例子说明:

场景 i-1 max(1, i-1) i+1 min(n, i+1) 最终遍历的行范围
当前在第 1 行(i=1) 0 1 2 2(假设 n≥2) 1~2 行
当前在中间行(i=2) 1 1 3 3(假设 n≥3) 1~3 行
当前在最后一行(i=n) n-1 n-1 n+1 n n-1~n 行

列方向的 max(1, j-1)min(m, j+1) 逻辑完全一样,比如:

  • 第 1 列(j=1):列范围是 1~2;
  • 中间列(j=2):列范围是 1~3;
  • 最后一列(j=m):列范围是 m-1~m。

<3> 举例理解:

假设输入是 3 行 3 列(n=3, m=3),当前要计算的位置是 (2,2)(第二行第二列):

  1. 计算 _i 的范围:max(1,2-1)=1min(3,2+1)=3_i 遍历 1、2、3;
  2. 计算 _j 的范围:max(1,2-1)=1min(3,2+1)=3_j 遍历 1、2、3;
  3. 内层循环会遍历这 9 个位置:(1,1)(1,2)(1,3)(2,1)(2,2)(2,3)(3,1)(3,2)(3,3)
  4. 每遍历一个位置,若 a[_i][_j]=1(地雷),ans[2][2] 就加 1。

再举一个边界例子:当前位置是 (1,1)(第一行第一列):

  1. _i 范围:max(1,1-1)=1min(3,1+1)=2 → 遍历 1、2;
  2. _j 范围:max(1,1-1)=1min(3,1+1)=2 → 遍历 1、2;
  3. 内层循环只遍历 4 个位置:(1,1)(1,2)(2,1)(2,2)(避免了访问 (0,0) 这种不存在的位置)。

2.灌溉 - 蓝桥云课

农田灌溉问题解题思路

  1. 初始化 :用数组a记录初始出水点(标记为 1),数组b作为临时数组存储下一分钟灌溉状态;
  2. 模拟扩散 :循环 k 次(对应 k 分钟),每轮遍历a数组,将所有已灌溉格子的上下左右及自身在b中标记为 1,再将b的状态同步到a(保证每轮扩散基于上一轮状态);
  3. 统计结果 :遍历a数组,统计值为 1 的格子数量,即为 k 分钟后灌溉总面积。

3.回文日期 - 蓝桥云课(复杂)

三、进制转换

<1> 进制

进制是数值的表示规则,核心是「逢 N 进 1」(N为进制数,也就是基数)。最常用的是二进制、八进制、十进制、十六进制,进制转换则是不同进制间数值表示形式的转换。

进制名称 基数 进位规则 数字范围 位权规则(第 n 位) 编程标识 示例 → 十进制
十进制 10 逢 10 进 1 0-9 10^(n-1) 123 → 123
二进制 2 逢 2 进 1 0、1 2^(n-1) 0b 0b101 → 5
八进制 8 逢 8 进 1 0-7 8^(n-1) 0 012 → 10
十六进制 16 逢 16 进 1 0-9、A-F(A=10,F=15) 16^(n-1) 0x 0x1A → 26

<2> 进制转换

所有进制转换都以十进制为中间桥梁

1.任意进制 → 十进制:按权展开求和

从右往左数,第 n 位的权重 = 基数ⁿ⁻¹,无论哪种进制转十进制,本质都是**"每一位数字 × 这一位的权重(基数的幂次),再求和"**。

进制类型 基数 位权计算公式(第 n 位) 典型数值示例(十进制 153)
十进制 10 10ⁿ⁻¹ 153 ​= 1 ×10²+5 ×10¹+3×10⁰
二进制 2 2ⁿ⁻¹ 10011001 = 1 ×2⁷+0 ×2⁶+⋯+1×2⁰
八进制 8 8ⁿ⁻¹ 231 = 2 ×8²+3 ×8¹+1×8⁰
十六进制 16 16ⁿ⁻¹ 99 = 9 ×16¹+9×16⁰

举例

  1. 二进制 10011001 → 十进制:1×2⁷+0×2⁶+⋯+1×2⁰ = 128+16+8+1 = 153

  2. 八进制 231 → 十进制:2×8²+3×8¹+1×8⁰ = 128+24+1 = 153

  3. 十六进制 99​ → 十进制:9×16¹+9×16⁰ = 144+9 = 153

2.十进制 → 任意进制:除基数,取余数,逆序排列

  1. 用十进制数除以基数 k,记录余数(因为按照权重求和公式,取模得到的是最后一位数字)

  2. 商继续除以 k,重复记录余数

  3. 商为 0 时停止,将余数逆序排列得到结果

    153 ÷ 2 = 76 余 1
    76 ÷ 2 = 38 余 0
    38 ÷ 2 = 19 余 0
    19 ÷ 2 = 9 余 1
    9 ÷ 2 = 4 余 1
    4 ÷ 2 = 2 余 0
    2 ÷ 2 = 1 余 0
    1 ÷ 2 = 0 余 1
    余数逆序:10011001 → 153​=10011001

3.非十进制之间互转:先转十进制,再转目标进制(或用二进制分组法快速转换↓)

转换方向 分组规则 示例
二进制 → 八进制 从右往左每 3 位 一组,不足补前导 0 10011001​ → 010 011 001 → 231​
二进制 → 十六进制 从右往左每 4 位 一组,不足补前导 0 10011001​ → 1001 1001 → 99
八进制 → 二进制 每 1 位八进制 → 3 位二进制 231​ → 2(010) 3(011) 1(001) → 10011001​
十六进制 → 二进制 每 1 位十六进制 → 4 位二进制 99​ → 9(1001) 9(1001) → 100110012

模板

1.任意进制转十进制:

复制代码
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
int main()
{
	//任意进制转换为十进制 
	ll x=0;
	int k;cin>>k;//k表示k进制 
	int n;cin>>n;//数组有效长度 
	int a[]={0,1,3,10,5,7};//a[]表示一个k进制的整数 
	//得到a[]的十进制数 
	for(int i=1;i<=n;i++)
	{
		x=x*k+a[i];
	}
	cout<<x<<'\n'; 
	return 0;
 } 
循环次数 i 执行语句 循环后 x 的值
初始状态 - x=0
i=1 x=0×k + a[1] x = 1
i=2 x=1×k + a[2] x = 1×k + 3
i=3 x=(1×k+3)×k +10 x = 1×k² + 3×k +10
i=4 x=(1×k²+3k+10)×k +5 x = 1×k³ + 3×k² +10×k +5
i=5 x=(1×k³+3k²+10k+5)×k +7 x = 1×k⁴ + 3×k³ +10×k² +5×k +7

2.十进制转任意进制:

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

int main() {
    ll x;
    int k;
    cin >> x >> k;

    ll a[100];   //定义数组 a 用于存储每一位的余数
    int cnt = 0; // 记录有效数字的个数(下标从1开始)

    // 除基取余
    while (x) {
        // 先自增 cnt,再存余数,确保数组从下标 1 开始存储
        a[++cnt] = x % k; 
        x = x / k;
    }

    // 反转数组包含下标 1 到 cnt 的所有元素
    reverse(a + 1, a + 1 + cnt);

    for (int i = 1; i <= cnt; i++) {
        // 处理大于9的数字,如 10->A, 11->B...
        if (a[i] >= 10) {
            cout << (char)('A' + a[i] - 10);
        } else {
            cout << a[i];
        }
    }
    cout << endl;

    return 0;
}

相关练习

1.进制 - 蓝桥云课

2.九进制转十进制 - 蓝桥云课

3.进制转换 - 蓝桥云课

前缀和

前缀和是编程中预处理数组、快速求解区间和的算法,思想是通过提前计算 "前 n 个元素的累加和",将多次区间和查询的时间复杂度从 O(n) 降至 O(1)。

  • 前缀和数组 prefix[] 是基于用户输入数组 a[](下标从 1 开始)生成的。其中每个元素 prefix[i] 表示从 a[1] 到 a[i] 的累加和,即: prefix[i] = a[1] + a[2] + ... + a[i]
  • 前缀和数组具有递推性质,可通过以下方式快速计算: prefix[i] = prefix[i-1] + a[i]
  • 利用前缀和数组,我们可以在 O(1) 计算任意区间 [l, r] 的和:sum(l, r) = prefix[r] - prefix[l-1]

注意

prefix 是一种预处理算法,只适用于 a 数组为静态数组的情况,即 a 数组中的元素在区间和查询过程中不会进行修改。如果需要实现 "先区间修改,再区间查询" 可以使用差分数组,如果需要 "一边修改,一边查询" 需要使用树状数组或线段树等数据结构。

模板

复制代码
#include <iostream>
#include <vector>
using namespace std;

int main() {
    // 1. 输入数组长度n和查询次数q
    int n, q;
    cin >> n >> q;

    // 2. 定义原数组a,下标从1开始,a[0] = 0(占位/边界值)
    vector<int> a(n + 1, 0);
    for (int i = 1; i <= n; ++i) {
        cin >> a[i];
    }

    // 3. 定义前缀和数组prefix,prefix[0] = 0(边界值,方便计算)
    vector<int> prefix(n + 1, 0);
    // 核心循环:从前往后递推计算前缀和
    for (int i = 1; i <= n; ++i) {
        prefix[i] = prefix[i - 1] + a[i];
    }

    // 4. 处理多次区间和查询
    while (q--) {
        int L, R;
        cin >> L >> R;
        // 区间和公式:sum(L,R) = prefix[R] - prefix[L-1]
        cout << prefix[R] - prefix[L - 1] << endl;
    }

    return 0;
}

输入:
5 2       // 数组长度5,2次查询
1 2 3 4 5 // 原数组a[1]~a[5]
1 3       // 第一次查询区间[1,3]
2 5       // 第二次查询区间[2,5]

输出:
6         // 1+2+3=6
14        // 2+3+4+5=14

相关练习

1.区间次方和 - 蓝桥云课

1.小郑的蓝桥平衡串 - 蓝桥云课

差分

差分算法是针对静态数组批量区间修改的算法,核心思想是通过 "记录数组相邻元素的差值",将原本需要 O(n) 时间的区间修改操作优化为 O(1),最终通过一次前缀和还原得到修改后的数组,是与前缀和互补的基础算法。

  • 对于原数组 a,其差分数组 diff 满足:

diff[i] = a[i] - a[i-1](其中规定 a[0] = 0,避免边界特判)

  • 前缀和还原:对差分数组做前缀和运算可还原原数组。

即:a[i] = diff[1] + diff[2] + ... + diff[i] = Σ(diff[1..i])

  • 区间修改原理 :在差分数组上执行 diff[l] += x 且 diff[r+1] -= x

等价于在原数组的区间 [l, r] 内所有元素加 x(若 r+1 超出数组长度则无需执行 diff[r+1] -= x

注意

差分数组仅支持批量修改后批量查询,实时查询需使用树状数组或线段树等数据结构。

模板

复制代码
#include <iostream>
#include <vector>
using namespace std;

int main() {
    // 1. 输入数组长度n
    int n;
    cin >> n;

    // 2. 定义原数组a,下标从1开始,a[0] = 0(占位/边界值)
    vector<int> a(n + 1, 0);
    for (int i = 1; i <= n; ++i) {
        cin >> a[i];
    }

    // 3. 定义差分数组diff,diff[0] = 0
    vector<int> diff(n + 2, 0); // 多开1位,避免r+1越界
    // 核心循环:构建差分数组,时间复杂度O(n)
    for (int i = 1; i <= n; ++i) {
        diff[i] = a[i] - a[i - 1];
    }

    // 4. 输入区间修改操作:将区间[l, r]所有元素加x
    int l, r, x;
    cin >> l >> r >> x;
    // 差分数组区间修改核心操作:O(1)时间完成区间加
    diff[l] += x;
    diff[r + 1] -= x;

    // 5. 还原修改后的原数组(前缀和还原)
    vector<int> new_a(n + 1, 0);
    for (int i = 1; i <= n; ++i) {
        new_a[i] = new_a[i - 1] + diff[i];
    }

    // 6. 输出修改后的数组
    for (int i = 1; i <= n; ++i) {
        cout << new_a[i] << " ";
    }
    cout << endl;

    return 0;
}

输入:
5          // 数组长度5
1 2 3 4 5  // 原数组a[1]~a[5]
2 4 3      // 区间[2,4]加3

输出:
1 5 6 7 5  // 修改后的数组:[1, 2+3, 3+3, 4+3, 5] = [1,5,6,7,5]

相关练习

1.区间更新 - 蓝桥云课

2.小明的彩灯 - 蓝桥云课

前缀和与差分

维度 前缀和(Prefix Sum) 差分(Difference Array)
核心目标 快速查询任意区间 [l,r] 的元素和 快速对任意区间 [l,r] 做统一修改(如加 x)
数组定义 原数组 a [1~n](下标从 1 开始),前缀和数组 s [0~n]:s[0] = 0``s[i] = a[1]+a[2]+...+a[i] 原数组 a [1~n](下标从 1 开始),差分数组 d [0~n+1]:d[0] = 0``d[i] = a[i] - a[i-1]
核心公式 1. 构建:s[i] = s[i-1] + a[i](递推累加)2. 查询:sum(l,r) = s[r] - s[l-1] 1. 构建:d[i] = a[i] - a[i-1](求差值)2. 修改:d[l] += x + d[r+1] -= x
逆运算 - 还原原数组:a[i] = a[i-1] + d[i](前缀和还原)
时间复杂度 构建 O (n),查询 O (1) 构建 O (n),修改 O (1),还原 O (n)

贪心

贪心算法的核心逻辑是每一步都做出当前看来最优的选择(局部最优),试图通过一系列局部最优决策得到全局最优解。它不像动态规划那样考虑所有可能的情况,而是 "走一步看一步",追求当下的最优,因此实现简单、效率高,但并非所有问题都能通过贪心得到正确的全局最优解。

"贪心选择性质" + "最优子结构" 是贪心算法能生效的两个关键前提:

  • 贪心选择性质:每一步的局部最优选择,无需回溯,直接导向全局最优;
  • 最优子结构:问题的全局最优解可以由若干子问题的最优解组合而成。

贪心算法的解题步骤

  • 确定问题的最优子结构 (贪心往往和**++排序、优先队列++**等一起出现)。
  • 构建贪心选择的策略,可能通过 "分类讨论"、"最小代价"、"最大价值" 等方式来思考贪心策略。简单验证贪心的正确性,采用句式一般是:这样做一定不会使得结果变差、不存在比当前方案更好的方案等等。
  • 通过贪心选择逐步求解问题,直到得到最终解。

相关练习

1.最小化战斗力差距 - 蓝桥云课

2.谈判 - 蓝桥云课

3.纪念品分组 - 蓝桥云课

4.分糖果 - 蓝桥云课

双指针

双指针是一种常用的数组 / 字符串优化算法思想,通过使用两个指针在同一序列上以不同方式移动,快速完成查找、匹配、排序、移动等操作。把原本需要多重循环的 O(n2) 复杂度问题降为 O(n),实现高效求解。

双指针并非 C++ 中真正的指针类型,而是用两个整型变量表示数组 / 字符串的下标,通过控制两个下标的移动逻辑来实现遍历优化,后续算法描述中统一用 "指针" 代指下标变量。

双指针往往也和单调性、排序联系在一起,在数组的区间问题上,暴力法的时间复杂度往往是 O (n^2) 的,但双指针利用 "单调性" 可以优化到 O (n)。

常见双指针模型

1. 对撞指针(左右指针)

  • 两个指针分别从数组最左端和最右端开始。
  • 一个向右走,一个向左走,向中间对撞,直到相遇。
  • 适合有序数组的查找、求和、反转等问题。

对撞指针求解步骤

  1. 使用两个指针 left,right。left 指向序列第一个元素,即:left = 1,right 指向序列最后一个元素,即:right = n。
  2. 在循环体中将左右指针相向移动,当满足一定条件时,将左指针右移,left ++。当满足另外一定条件时,将右指针左移,right --。
  3. 直到两指针相撞(即 left == right),或者满足其他要求的特殊条件时,跳出循环体。

2. 快慢指针(龟兔指针)

  • 两个指针都从数组左端 出发,同向移动
  • 移动的步长一个快一个慢。
  • 适合链表的找环、找中点、以及有序数组去重等问题。

快慢指针求解步骤

  1. 使用两个指针 l、r。l 一般指向序列第一个元素,即:l = 1,r 一般指向序列第零个元素,即:r = 0。即初始时区间 [l, r] = [1, 0] 表示为空区间。
  2. 在循环体中将左右指针向右移动。当满足一定条件时,将慢指针右移,即 l ++。当满足另外一定条件时(也可能不需要满足条件),将快指针右移,即 r ++,保持 [l, r] 为合法区间。
  3. 到指针移动到数组尾端(即 l == n 且 r == n),或者两指针相交,或者满足其他特殊条件时跳出循环体。

3.滑动窗口

用两个指针 i 和 j 表示一个区间 [i, j],j往右走扩大窗口,i 往右缩小窗口,一遍遍历解决子数组 / 子串问题,时间复杂度 O (n)。

相关练习

1.美丽的区间 - 蓝桥云课

2.挑选子串 - 蓝桥云课

二分

二分算法是一种在有序数据上进行快速查找的高效算法,核心思想是每次将查找范围缩小一半,把时间复杂度从暴力的 O(n) 降到 (O (log n),是算法中最经典的 "减治" 思想之一。

二分算法必须作用在有序序列上(升序或降序),无序数组无法使用二分,这是二分成立的根本条件。

解题步骤:

1. 确认单调性

**2. 确定搜索区间 [l, r]:**设定初始左右边界,必须保证目标答案 / 分界点在区间内

若最终答案以 r 为基准,答案区间为 [l+1, r]

若最终答案以 l 为基准,答案区间为 [l, r-1]

整数二分建议区间从 1 开始,避免越界;

二分答案需根据问题设定合理上下界(如最大可能值、最小可能值)。

3. 设计 check 函数: 传入中间值 mid,判断 mid 属于「满足条件的区间」还是「不满足条件的区间」,从而决定指针移动方向;

**4. 计算中间点 mid:**mid = (l + r) / 2

整数二分:向下取整;

浮点二分:直接取中点,控制精度循环。

5. 根据 check 结果和单调性,移动指针,缩小区间

check(mid) 为真(满足条件):根据需求更新边界(如找左边界则 r=mid,找右边界则 l=mid);

check(mid) 为假(不满足条件):更新另一侧边界(如 l=mid+1r=mid-1);

整数二分需注意避免死循环,统一循环条件(如 while(l < r)while(l <= r))。

6. 输出最终答案

循环结束后,根据题意返回 lr

第一个≥目标值 的左边界:返回 r

最后一个≤目标值 的右边界:返回 l

二分答案:lr 最终重合,返回任意一个即可。

类型

1. 整数二分

已有的有序整数数组上进行二分查找,核心是找元素位置、分界点(如第一个≥x、最后一个≤x 的位置),是最基础的二分类型。模板如下:

复制代码
// 找到升序数组a中的x第一次出现的位置
int l = 0, r = 1e9;
// 注意这里的判断条件,这样可以保证l,r最终一定收敛到分界点
while(l + 1 != r) // l,r相邻退出
{
    int mid = (l + r) / 2;

    // 如果a为升序,说明mid偏大了,需要减小mid,就只能将r变小,即r = mid
    if(a[mid] >= x)r = mid;
    else l = mid;
}

cout << r << '\n';

2. 浮点二分

实数(浮点数)区间上进行二分,因为实数域本身是单调的,所以也满足单调性,和整数二分的主要区别在于使用的变量类型、退出的判断条件不同。模板如下:

复制代码
// 计算单调函数f(x)的零点
double l = 0, r = 1e9, eps = 1e-6;
// 注意这里的判断条件,这样可以保证l,r最终一定收敛到分界点
while(r - l >= eps) // eps是一个极小量,设置为1e-6较合适
{
    double mid = (l + r) / 2;

    // f(x)单调递增,f(mid) >= 0,说明mid偏大了,需要减小mid,就只能将r变小,即r = mid
    if(f(mid) >= 0)r = mid;
    else l = mid;
}

// 最后返回l,r差别不大
cout << r << '\n';

3. 二分答案

不直接在数组上二分,而是将答案本身作为二分变量,通过验证「mid 是否满足题目条件」,利用答案的单调性缩小区间,最终找到最优答案,是最常用的二分类型。

复制代码
bool check(int mid)
{
    bool res = true;
    //do something to check the authority of mid...
    return res;
}

int main()
{
    int l = 0, r = 1e9; //范围
    while(l + 1 != r)
    {
        int mid = (l + r) / 2;
        //具体写法需要根据题意修改
        if(check(mid))
            l = mid;
        else 
            r = mid;
    }

    cout << l << '\n';//具体输出的内容需要根据题意判断
}

相关练习

1.二分查找数组元素 - 蓝桥云课

2.跳石头 - 蓝桥云课

3.肖恩的苹果林 - 蓝桥云课

4.肖恩的乘法表 - 蓝桥云课

位运算

位运算是直接对二进制的位进行操作的运算方式,位运算中每一位都相互独立 ,各自运算得出结果(左右移除外)。在计算机科学和编程中,位运算常用于优化算法、位掩码操作、位字段处理等领域。在竞赛中,经常考察异或的性质、状态压缩、与位运算有关的特殊数据结构、构造题等。仅适用于非负整数,不可用于字符、浮点数。

<1> 整数的二进制表示

在计算机中,整数是通过补码表示的,一般情况下,对负数进行位运算意义不大,大多数都是对正整数进行处理,而正数的原码 = 补码,所以我们直接考虑二进制数的原码,也就是直接地表示二进制数。例如整数 10,在计算机中存储如下(按照书写习惯,一般认为右边为低位,在左右移时尤为重要):

val(二进制) 0 0 0 0 ... 1 0 1 0
idx(下标) 31 30 29 28 ... 3 2 1 0

<2> 常见位运算

1. 按位与 (&)

按位与运算符(&)用于对两个操作数的对应位进行逻辑与操作。

  • 运算规则:只有当两个位都为 1 时,结果位才为 1,否则为 0。
  • 特点:两个数字做与运算,结果不会变大。
x y x&y
0 0 0
0 1 0
1 0 0
1 1 1
数值 二进制
6 0 1 1 0
11 1 0 1 1
6 & 11 = 2 0 0 1 0(十进制 2)

2. 按位或 (|)

按位或运算符(|)用于对两个操作数的对应位进行逻辑或操作。

  • 运算规则:只要两个位中有一个为 1,结果位就为 1,否则为 0。
  • 特点:两个数字做或运算,结果不会变小。
x y x|y
0 0 0
0 1 1
1 0 1
1 1 1
数值 二进制
6 0 1 1 0
11 1 0 1 1
6 | 11 = 15 1 1 1 1(十进制 15)

3. 按位异或 (^)

按位异或运算符(^)用于对两个操作数的对应位进行逻辑异或操作。

  • 运算规则:当两个位不同时,结果位为 1,否则为 0。
  • 特点:两个数字做异或运算,结果可能变大也可能变小,也可能不变,不会进位。
x y x^y
0 0 0
0 1 1
1 0 1
1 1 0
数值 二进制
6 0 1 1 0
11 1 0 1 1
6 ^ 11 = 13 1 1 0 1(十进制 13)

异或的性质

  • 交换律:x ^ y = y ^ x
  • 结合律:x ^ (y ^ z) = (x ^ y) ^ z
  • 自反性:x ^ x = 0
  • 零元素:x ^ 0 = x
  • 逆运算:x ^ y = z,则有 z ^ y = x(两边同时异或 y,抵消掉)

4. 按位取反(~)

用于对操作数的每一位进行取反操作,即将 0 变为 1,将 1 变为 0。

按位取反操作通常用于无符号整数(unsigned int/long long),这是为了避免符号位取反造成干扰。假设某个无符号整数只有 4 位,结果如下(长度不同结果也会不同,需要具体情况具体分析):

x ~x
0 1
1 0
数值 二进制
6 0 1 1 0
~6 = 9 (4 位无符号整数) 1 0 0 1(十进制 9)

5. 按位左移(<<)

左移(<<)操作将一个数的二进制表示向左移动指定的位数。

  • 移动后,低位补 0 ,如果数据类型为有符号整型,注意移动的时候不要移动到符号位上,或者干脆使用无符号整型(unsigned int)(1 会移动到符号位上)。
  • 特点:左移操作相当于对原数进行乘以 2 的幂次方的操作。

示例:整数 5(二进制 00000101)左移 3 位,等价于 5 * (2^3) = 40

阶段 二进制
原数 5 0 0 0 0 0 1 0 1
5 << 3 0 0 1 0 1 0 0 0(十进制 40)

6. 按位右移(>>)

右移(>>)操作将一个数的二进制表示向右移动指定的位数。

  • 移动后,一般情况高位补 0 ,如果数据类型为有符号整型,注意移动的时候让符号位为 0,或者干脆使用无符号整型(unsigned int)。如果符号位上有 1 不会被移走。
  • 特点:右移操作相当于对原数进行除以 2 的幂次方的操作(向下取整)。

示例:整数 13(二进制 00001101)右移 2 位,等价于 13 / 4 = 3(向下取整)

阶段 二进制
原数 13 0 0 0 0 1 1 0 1
13 >> 2 0 0 0 0 0 0 1 1(十进制 3)

运算类型 运算符 核心规则 特点 等价操作
按位与 & 两位都为 1 则为 1,否则为 0 结果不会变大 清零指定位、判断奇偶
按位或 | 有一位为 1 则为 1,否则为 0 结果不会变小 置 1 指定位
按位异或 ^ 两位不同则为 1,相同则为 0 结果可大可小,无进位 交换数、消重、翻转位
按位取反 ~ 0 变 1,1 变 0 符号位敏感,建议无符号使用 构造掩码、取反操作
左移 << 左移指定位,低位补 0 等价 ×2ⁿ 快速乘 2 的幂
右移 >> 右移指定位,高位补 0 等价 ÷2ⁿ(向下取整) 快速除 2 的幂

<3> 位运算技巧

1. 判断数字奇偶x & 1 结果为 1 则为奇数,否则为偶数。

2. 获取二进制数的某一位(x >> i) & 1 可获取第 i 位值(0 或 1)。

3. 修改二进制中的某一位为 1x | (1 << i) 将第 i 位置 1。清零操作:x & ~(1 << i) 将第 i 位置 0。

4. 快速判断一个数字是否为 2 的幂次方 :若 x & (x - 1) == 0 且 x ≠ 0,则 x 为 2 的幂。

如果 x 为 2 的幂次方,则 x 的二进制表示中只有一个 1,x - 1 就有很多个连续的 1 并且和 x 的 1 没有交集,两者与运算一定为 0,可以证明其他情况必然不为 0。

5. 获取二进制位中最低位的 1 :公式 lowbit(x) = x & -x,如果 x = (0100010),则 lowbit (x) = (0000010),常用于树状数组。

相关练习

1.二进制中 1 的个数 - 蓝桥云课

2.区间或 - 蓝桥云课

3.异或森林 - 蓝桥云课

递归

递归是一种通过函数调用自身来解决问题的算法思想,核心是将复杂的大问题拆解为结构相同的小问题,直到小问题简化为可直接求解的 "基线条件(终止条件)",再通过逐层返回结果,最终解决原问题。

复制代码
返回类型 函数名(参数列表) {
    // 基本情况(递归终止条件)
    if (满足终止条件) {
        // 返回终止条件下的结果
    }
    // 递归表达式(递归调用)
    else {
        // 将问题分解为规模更小的子问题
        // 使用递归调用解决子问题
        // 返回子问题的结果
    }
}

斐波那契数列

已知 F (1) = F (2) = 1;n > 3 时 F (n) = F (n - 1) + F (n - 2)

输入 n,求 F (n),n ≤ 100000,结果对 1e9 + 7 取模

复制代码
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 1e5 + 9;    // 数组最大长度,适配题目n<=1e5的输入范围,+9是防止边界越界
const ll p = 1e9 + 7;     // 模数:1e9+7是竞赛常用大质数,用于取模防止数值溢出
ll dp[N];                 // 全局备忘录数组:存储已计算的斐波那契值,全局变量默认初始值为0

ll fib(int n)
{
    if(dp[n]) return dp[n]; // 如果dp[n]非0,说明该位置已计算过,直接返回结果
    if(n <= 2) return 1;    // 递归终止条件:F(1)=1,F(2)=1,避免无限递归
    // 递归计算:F(n) = F(n-1) + F(n-2)
    // 取模操作:每一步都%p,防止数值溢出。结果存入dp数组:下次调用直接读取,消除重复计算
    return dp[n] = (fib(n - 1) + fib(n - 2)) % p;
}

int main()
{
    int n; cin >> n;      // 定义变量n,存储输入的项数  
    // 循环输出1~n项的斐波那契值,每行一个结果
    for(int i = 1; i <= n; ++i)cout << fib(i) << '\n';  // 调用fib函数计算第i项并输出
    
    return 0;       
}

1  // F(1)
1  // F(2)
2  // F(3)=F(2)+F(1)
3  // F(4)=F(3)+F(2)
5  // F(5)=F(4)+F(3)

相关练习

1.数的计算 - 蓝桥云课

离散化

离散化是一种针对值域大但数量少 的数组的算法,核心是将「大范围、稀疏分布」的数值映射为「小范围、连续」的整数。将值域大、元素少、有重复 的原数组,压缩为有序、去重的小数组,保留数值相对大小。一般不会单独考察,通常搭配树状数组、线段树、二维平面的计算几何考察。

离散化数组要求必须有序 ,一般是去重的

双向映射

下标→值:直接通过离散化数组下标访问对应值;

值→下标:通过 lower_bound二分查找实现映射。

步骤:原数组复制 → 排序 → 去重,生成离散化数组;再用二分完成原数值到下标的映射。

|---------------------|--------------------------------------------|
| 原数组 | [3, 1000, 2, 99999, 2] |
| 离散化数组 L(排序去重后) | [2, 3, 1000, 99999] |
| 下标→值映射 | L[0]=2,L[1]=3,L[2]=1000,L[3]=99999 |
| 值→下标映射(lower_bound) | 2→0,3→1,1000→2,99999→3 |

模板

复制代码
#include <bits/stdc++.h>
using namespace std;

const int N = 1e5 + 9;
int a[N];          // 原数组:存储原始输入数据
vector<int> L;     // 离散化数组:存储排序、去重后的元素

// 映射函数:返回原数值x在离散化数组L中的下标
int getidx(int x)
{
    return lower_bound(L.begin(), L.end(), x) - L.begin();
}

int main()
{
    int n; cin >> n;
    for(int i = 1; i <= n; ++i)cin >> a[i];      //存入原数组
    for(int i = 1; i <= n; ++i)L.push_back(a[i]);// 将原数组元素存入离散化数组L

    sort(L.begin(), L.end());                    //排序
    L.erase(unique(L.begin(), L.end()), L.end());//去重
    
    cout << "离散化数组为:";                     //输出离散化数组
    for(const auto &i : L)cout << i << ' ';
    cout << '\n';
    
    int val; cin >> val;
    cout << getidx(val) << '\n';                //输入原数值,输出其离散化下标
    
    return 0;
}

构造

构造算法是直接根据问题条件,设计特定规则 / 结构来求解的算法,不依赖通用模板(如贪心、动态规划),而是通过分析问题特征,手动设计满足条件的解。

相关推荐
bearpping2 小时前
MacOs安装Redis并设置为开机、后台启动
redis·macos·蓝桥杯
96772 小时前
多线程编程:整个互斥的流程以及scoped_lock的用法,以及作用,以及 硬件上的原子操作和逻辑上的原子操作
开发语言·c++·算法
liuyao_xianhui2 小时前
优选算法_topk问题_快速排序算法_堆_C++
java·开发语言·数据结构·c++·算法·链表·排序算法
liuyao_xianhui2 小时前
优选算法_堆_最后一块石头的重量_C++
java·开发语言·c++·算法·链表
羊小猪~~2 小时前
算法/力扣--栈与队列经典题目
开发语言·c++·后端·考研·算法·leetcode·职场和发展
扶摇接北海1762 小时前
洛谷:P1035 [NOIP 2002 普及组] 级数求和
算法
WitsMakeMen2 小时前
RankMixer论文理解
算法
qqqahhh2 小时前
harness engineering学习
笔记·学习
NULL指向我2 小时前
信号处理学习笔记5:卡尔曼滤波理论
笔记·学习·信号处理