其他问题
一、搜索算法
1.1 深度优先搜索(DFS)
基本概念
深度优先搜索是一种沿着树的深度遍历节点的算法,尽可能深地搜索树的分支。
应用场景
- 图的连通分量
- 排列组合生成
- 棋盘类问题
- 回溯算法
例题1.1:八皇后问题
问题描述:在 \(8 \times 8\) 的棋盘上放置8个皇后,使得它们互不攻击(任意两个皇后不在同一行、同一列或同一对角线上)。
解题思路:
- 使用DFS按行放置皇后
- 使用三个布尔数组标记列、主对角线、副对角线是否被占用
- 对角线编号技巧:主对角线 \(row-col+7\),副对角线 \(row+col\)
- 时间复杂度:\(O(n!)\),但通过剪枝可以大大减少搜索空间
例题1.2:数独求解
问题描述:解一个 \(9 \times 9\) 的数独,保证有唯一解。
解题思路:
- 使用DFS回溯
- 每次选择可填数字最少的格子(剪枝优化)
- 使用位运算加速判断可填数字
- 时间复杂度:指数级,但实际运行很快
1.2 广度优先搜索(BFS)
基本概念
广度优先搜索是一种逐层遍历的算法,先访问离起点最近的节点。
应用场景
- 无权图的最短路径
- 状态空间搜索
- 连通分量
- 拓扑排序
例题1.3:迷宫最短路径
问题描述:给定一个 \(n \times m\) 的迷宫,'.'表示通路,'#'表示障碍,求从起点到终点的最短步数。
解题思路:
- 使用BFS从起点开始扩展
- 使用队列存储待访问节点
- 使用距离数组记录每个位置到起点的距离
- 时间复杂度:\(O(nm)\)
例题1.4:八数码问题
问题描述:在 \(3 \times 3\) 的棋盘上,有8个方块和一个空格。每次可以将空格与相邻方块交换,求从初始状态到目标状态的最少步数。
解题思路:
- 将状态编码为字符串或整数
- 使用BFS搜索状态空间
- 使用哈希表记录已访问状态
- 使用双向BFS优化
- 时间复杂度:状态数 \(9! = 362880\)
1.3 迭代加深搜索(IDDFS)
基本概念
结合DFS的空间效率和BFS的完备性,逐步增加深度限制进行DFS。
应用场景
- 状态空间大但解深度小的问题
- 不知道目标深度的情况
例题1.5:IDA*求解八数码
问题描述:使用迭代加深A*算法解决八数码问题。
解题思路:
- 使用曼哈顿距离作为启发函数
- 逐步增加深度限制
- 使用估值函数剪枝:\(f(s) = g(s) + h(s)\)
- 当 \(f(s) > depth\) 时剪枝
1.4 双向搜索
基本概念
从起点和终点同时开始搜索,在中间相遇。
应用场景
- 状态空间指数级增长的问题
- 知道起点和终点的搜索问题
例题1.6:15数码问题
问题描述:\(4 \times 4\) 的棋盘上15个方块和一个空格,求从初始状态到目标状态的最少步数。
解题思路:
- 状态空间巨大:\(16! \approx 2 \times 10^{13}\)
- 使用双向BFS
- 使用哈希表存储两个方向的已访问状态
- 遇到交集时得到答案
二、贪心算法
2.1 基本概念
贪心算法每一步都选择当前最优解,希望最终得到全局最优解。
适用条件
- 最优子结构
- 贪心选择性质
例题2.1:区间调度
问题描述:有 \(n\) 个活动,每个活动有开始时间 \(s_i\) 和结束时间 \(f_i\)。求最多能安排多少个互不冲突的活动。
解题思路:
- 按结束时间从早到晚排序
- 选择结束时间最早且不与已选活动冲突的活动
- 时间复杂度:\(O(n \log n)\)
例题2.2:哈夫曼编码
问题描述:给定 \(n\) 个字符的出现频率,构建最优前缀编码,使编码总长度最小。
解题思路:
- 使用最小堆维护频率最小的两个节点
- 每次取出两个最小节点,合并为新节点
- 新节点频率为两者之和,放回堆中
- 重复直到只剩一个节点
- 时间复杂度:\(O(n \log n)\)
例题2.3:加油站问题
问题描述:环形路线上有 \(n\) 个加油站,每个加油站有油量 \(gas[i]\),到下一站耗油 \(cost[i]\)。判断是否能绕行一圈,并求起点。
解题思路:
- 如果总油量小于总耗油量,无解
- 从第一个加油站开始,累加剩余油量
- 如果剩余油量小于0,从下一站重新开始
- 时间复杂度:\(O(n)\)
三、分治算法
3.1 基本概念
将问题分解为若干子问题,递归求解,合并结果。
经典算法
- 归并排序
- 快速排序
- 最近点对
- 快速幂
例题3.1:归并排序求逆序对
问题描述:给定一个数组,求逆序对的数量。
解题思路:
- 在归并排序过程中统计逆序对
- 合并两个有序数组时,如果左数组元素大于右数组元素,则左数组剩余元素都与该右数组元素构成逆序对
- 时间复杂度:\(O(n \log n)\)
例题3.2:快速选择
问题描述:在未排序数组中找到第 \(k\) 小的元素。
解题思路:
- 类似快速排序,选择枢轴元素
- 将数组分为小于、等于、大于枢轴的三部分
- 根据 \(k\) 所在区间递归查找
- 期望时间复杂度:\(O(n)\)
例题3.3:平面最近点对
问题描述:给定平面上 \(n\) 个点,求距离最近的两个点。
解题思路:
- 按 \(x\) 坐标排序
- 分治:将点集分为左右两部分
- 递归求解左右两部分的最近点对
- 合并时检查跨分界线的点对
- 时间复杂度:\(O(n \log n)\)
四、二分与三分
4.1 二分查找
基本概念
在有序序列中查找满足条件的边界。
应用场景
- 有序数组查找
- 最大化最小值/最小化最大值
- 判定性问题
例题4.1:二分答案
问题描述:有 \(n\) 本书,每本书有 \(a_i\) 页。要抄写 \(m\) 本书,连续分配给 \(k\) 个人,每人抄写连续的书,求每人最多抄多少页。
解题思路:
- 二分每人最多抄的页数 \(x\)
- 贪心判断:按顺序分配,使每人不超 \(x\) 页
- 如果能分配完,则 \(x\) 可行,否则不可行
- 时间复杂度:\(O(n \log \sum a_i)\)
例题4.2:旋转数组最小值
问题描述:一个递增数组旋转后,求最小值。
解题思路:
- 二分查找,比较中间元素与右边界
- 如果中间元素小于右边界,最小值在左侧
- 如果中间元素大于右边界,最小值在右侧
- 如果相等,右边界左移
- 时间复杂度:\(O(\log n)\)
4.2 三分查找
基本概念
用于寻找单峰函数的极值。
应用场景
- 抛物线极值
- 单峰函数最值
例题4.3:函数最大值
问题描述:给定单峰函数 \(f(x)\),求在区间 \([l, r]\) 上的最大值。
解题思路:
- 取三等分点 \(m1 = l + (r-l)/3\),\(m2 = r - (r-l)/3\)
- 比较 \(f(m1)\) 和 \(f(m2)\)
- 如果 \(f(m1) < f(m2)\),极值在 \([m1, r]\),否则在 \([l, m2]\)
- 时间复杂度:\(O(\log (r-l))\)
五、前缀和与差分
5.1 前缀和
基本概念
\(pref[i] = \sum_{j=1}^{i} a[j]\)
应用场景
- 区间和查询
- 快速计算子矩阵和
例题5.1:子数组和为k
问题描述:给定一个整数数组和一个整数 \(k\),求有多少个子数组的和等于 \(k\)。
解题思路:
- 计算前缀和数组 \(pref\)
- 对于每个 \(i\),统计有多少 \(j < i\) 使得 \(pref[i] - pref[j] = k\)
- 使用哈希表存储前缀和出现次数
- 时间复杂度:\(O(n)\)
5.2 差分
基本概念
\(diff[i] = a[i] - a[i-1]\)
应用场景
- 区间加减操作
- 多次操作后查询
例题5.2:区间加法
问题描述:长度为 \(n\) 的数组,初始全为0。进行 \(m\) 次操作,每次给区间 \([l, r]\) 加上 \(k\)。求操作后的数组。
解题思路:
- 使用差分数组
- 操作 \([l, r] + k\) 转化为 \(diff[l] += k\),\(diff[r+1] -= k\)
- 最后对差分数组求前缀和得到原数组
- 时间复杂度:\(O(n+m)\)
六、排序与选择
6.1 排序算法
常见算法
- 快速排序:平均 \(O(n \log n)\),最坏 \(O(n^2)\)
- 归并排序:稳定 \(O(n \log n)\)
- 堆排序:原地 \(O(n \log n)\)
- 计数排序:\(O(n+k)\),k为值域
- 基数排序:\(O(d(n+k))\)
例题6.1:第k大元素
问题描述:在未排序数组中找到第 \(k\) 大的元素。
解题思路:
- 使用快速选择算法
- 使用堆:维护大小为 \(k\) 的最小堆
- 遍历数组,堆大小超过 \(k\) 时弹出最小元素
- 堆顶即为第 \(k\) 大元素
- 时间复杂度:\(O(n \log k)\)
例题6.2:合并区间
问题描述:给定多个区间,合并所有重叠的区间。
解题思路:
- 按区间左端点排序
- 遍历区间,如果当前区间与结果最后一个区间重叠则合并
- 否则加入结果
- 时间复杂度:\(O(n \log n)\)
七、位运算与状压
7.1 位运算技巧
常用操作
- 判断奇偶:
x & 1 - 取最低位的1:
x & -x - 去掉最低位的1:
x & (x-1) - 判断2的幂:
x & (x-1) == 0
例题7.1:子集枚举
问题描述:枚举集合的所有子集。
解题思路:
- 如果集合用二进制表示,子集枚举:
- 对于集合 \(mask\),子集 \(sub = mask\),每次 \(sub = (sub-1) & mask\)
- 直到 \(sub = 0\)
- 时间复杂度:\(O(2^n)\)
7.2 状态压缩DP
基本概念
用二进制位表示状态,常用于小规模集合问题。
例题7.2:旅行商问题
问题描述:\(n\) 个城市,求从起点出发经过所有城市一次并回到起点的最短路径。
解题思路:
- \(dp[mask][i]\) 表示已访问城市集合 \(mask\),当前在城市 \(i\) 的最短路径
- 状态转移:\(dp[mask][i] = \min(dp[mask \setminus \{i\}][j] + dist[j][i])\)
- 时间复杂度:\(O(n^2 \cdot 2^n)\)
八、构造与交互
8.1 构造题
特点
需要构造满足条件的解,通常有多种解法。
例题8.1:构造回文数组
问题描述:给定 \(n\),构造一个长度为 \(n\) 的数组,使得它是回文的,且所有元素互不相同,取值范围 \([1, 10^9]\)。
解题思路:
- 如果 \(n\) 为奇数,中间元素可以是任意值
- 从1开始递增填充两侧对称位置
- 如果 \(n > 2 \times 10^5\),需要特殊处理(但 \(n \leq 10^5\))
8.2 交互题
特点
程序与评测系统交互,需要根据反馈调整策略。
例题8.2:猜数字
问题描述:系统有一个 \([1, 10^9]\) 的整数,你可以询问一个数 \(x\),系统返回 \(x\) 与答案的关系(大于、小于、等于)。最多问50次。
解题思路:
- 二分查找
- 每次询问中间值
- \(10^9 < 2^{30}\),所以30次足够
- 交互格式:输出询问,刷新缓冲区,读取回答
九、模拟与实现
9.1 模拟题
特点
按照题意直接模拟过程,考察代码实现能力。
例题9.1:表达式求值
问题描述:给定一个包含加减乘除和括号的表达式,求值。
解题思路:
- 使用双栈:操作数栈和运算符栈
- 定义运算符优先级
- 中缀转后缀或直接计算
- 处理括号匹配
例题9.2:大整数运算
问题描述:实现大整数的加减乘除。
解题思路:
- 用字符串或数组存储数字(逆序)
- 加法:按位相加,处理进位
- 乘法:模拟竖式计算
- 除法:模拟长除法
十、随机化算法
10.1 基本概念
利用随机性解决问题的算法。
例题10.1:随机快速排序
问题描述:实现随机化的快速排序。
解题思路:
- 随机选择枢轴元素
- 避免最坏情况 \(O(n^2)\)
- 期望时间复杂度:\(O(n \log n)\)
例题10.2:蒙特卡洛方法
问题描述:用蒙特卡洛方法估算 \(\pi\)。
解题思路:
- 在正方形内随机撒点
- 统计落在内切圆内的点数
- \(\pi \approx 4 \times \frac{圆内点数}{总点数}\)
十一、近似算法与启发式
11.1 近似算法
在多项式时间内给出近似最优解的算法。
例题11.1:顶点覆盖近似算法
问题描述:给定无向图,求最小的顶点覆盖(近似解)。
解题思路:
- 近似算法:每次选择一条边,将两个端点加入覆盖,删除所有关联边
- 近似比:2
- 时间复杂度:\(O(n+m)\)
11.2 启发式算法
基于经验或直观的算法,如遗传算法、模拟退火等。
例题11.2:模拟退火求解TSP
问题描述:用模拟退火求解旅行商问题的近似解。
解题思路:
- 初始随机路径
- 定义邻域操作:交换两个城市
- 以一定概率接受劣解
- 温度逐渐降低
总结
学习建议
- 分类练习:按算法类型系统练习
- 理解本质:掌握每个算法的核心思想和适用场景
- 模板整理:总结常用算法的模板代码
- 综合应用:多做综合性题目,提高问题分析能力
常见考点
- 搜索算法的优化与剪枝
- 贪心算法的正确性证明
- 分治与递归的应用
- 二分与三分的灵活运用
- 构造题的思维训练
- 模拟题的代码实现能力
这些算法是OI竞赛的重要组成部分,需要大量的练习和思考。建议从基础题开始,逐步提高难度,掌握各类算法的应用场景和实现技巧。

浙公网安备 33010602011771号