OI-Wiki 学习笔记
1|0算法基础
1|1复杂度
定义
衡量一个算法的快慢,一定要考虑数据规模的大小。
一般来说,数据规模越大,算法的用时就越长。
而在算法竞赛中,我们衡量一个算法的效率时,最重要的不是看它在某个数据规模下的用时,而是看它的用时随数据规模而增长的趋势,即时间复杂度。
符号定义
相等是
大
对于函数
大
研究时间复杂度时通常会使用
大
同样的,我们使用
主定理
建议背下来,不是很好理解。
那么
需要注意的是,这里的第二种情况还需要满足
几个例子:
-
,那么 ,那么 可以取值在 之间,从而满足第一种情况,所以 。 -
,那么 ,那么 可以取值在 之间,从而满足第二种情况,所以 。 -
,那么 ,那么 可以取值为 ,从而满足第三种情况,所以 。 -
,那么 ,那么 可以取值为 ,从而满足第三种情况,所以 。
空间复杂度
类似地,算法所使用的空间随输入规模变化的趋势可以用空间复杂度来衡量。
1|2枚举
实际上一些题的正解就是枚举,但需要很多优化。
要点:
-
建立简洁的数学模型。
-
减少不必要的枚举空间。
-
选择合适的枚举顺序。
1|3模拟
模拟即为将题目描述中的操作用代码实现,码量一般比较大,写错比较难调,相当浪费时间。
写模拟题时有以下注意的点:
-
在写代码之前,尽量在演草纸上自习分析题目的实现过程。
-
在代码中尽量把每个部分抽离出来写成函数,模块化。
-
将题目中的信息转化,不要残留多种表达。
-
如果一次没过大样例,分块调试。
-
实现代码时按照演草纸上的思路一步步实现。
1|4递归
定义
我们可以用以下代码理解递归:
递归的优点
- 结构清晰、可读性强,可以参考归并排序的两种实现方式。
- 练习分析为题的结构,当发现问题可以分解成相同结构的小问题时,递归写多了就能敏锐发现这个特点,进而高效解决问题。
递归的缺点
在程序执行中,递归是利用堆栈来实现的。每当进入一个函数调用,栈就会增加一层栈帧,每次函数返回,栈就会减少一层栈帧。而栈不是无限大的,当递归层数过多时,就会造成栈溢出 的后果。
显然有时候递归处理是高效的,比如归并排序;有时候是低效的,比如数孙悟空身上的毛,因为堆栈会消耗额外空间,而简单的递推不会消耗空间。比如这个例子,给一个链表头,计算它的长度:
递归优化见后文搜索优化和记忆化搜索。
要点
明白一个函数的作用并相信它能完成这个任务,千万不要跳进这个函数里面企图探究更多细节,否则就会陷入无穷的细节无法自拔,人脑能压几个栈啊。
递归与枚举的区别
枚举是横向地把问题划分,然后依次求解子问题;而递归是把问题逐级分解,是纵向的拆分。
递归与分治的区别
递归是一种编程技巧,一种解决问题的思维方式;分治算法很大程度上是基于递归的,解决更具体问题的算法思想。
1|5分治
定义
就是把一个复杂的问题分成两个或更多的相同或相似的子问题,直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。
过程
分治算法的核心思想就是「分而治之」。
大概的流程可以分为三步:分解
-
分解原问题为结构相同的子问题。
-
分解到某个容易求解的边界之后,进行递归求解。
-
将子问题的解合并成原问题的解。
分治法能解决的问题一般有如下特征:
-
该问题的规模缩小到一定的程度就可以容易地解决。
-
该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质,利用该问题分解出的子问题的解可以合并为该问题的解。
-
该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子问题。
注意:如果各子问题是不独立的,则分治法要重复地解公共的子问题,也就做了许多不必要的工作。此时虽然也可用分治法,但一般用动态规划较好。
1|6贪心
适用范围
贪心算法在有最优子结构的问题中尤为有效。
最优子结构的意思是问题能够分解成子问题来解决,子问题的最优解能递推到最终问题的最优解。
证明
贪心算法有两种证明方法:反证法和归纳法。
一般情况下,一道题只会用到其中的一种方法来证明。
-
反证法:如果交换方案中任意两个元素/相邻的两个元素后,答案不会变得更好,那么可以推定目前的解已经是最优解了。
-
归纳法:先算得出边界情况(例如
)的最优解 ,然后再证明:对于每个 , 都可以由 推导出结果。
常见题型
在提高组难度以下的题目中,最常见的贪心有两种。
-
「我们将
按照某某顺序排序,然后按某种顺序(例如从小到大)选择。」。 -
「我们每次都取
中最大/小的东西,并更新 。」(有时「 中最大/小的东西」可以优化,比如用优先队列维护)
二者的区别在于一种是离线的,先处理后选择;一种是在线的,边处理边选择。
排序解法
用排序法常见的情况是输入一个包含几个(一般一到两个)权值的数组,通过排序然后遍历模拟计算的方法求出最优值。
后悔解法
思路是无论当前的选项是否最优都接受,然后进行比较,如果选择之后不是最优了,则反悔,舍弃掉这个选项;否则,正式接受。如此往复。
1|7排序
几种排序算法的比较
一张表格速通
排序方法 | 平均情况 | 最好情况 | 最坏情况 | 空间 | 稳定性 |
---|---|---|---|---|---|
选择排序 | 不稳定 | ||||
冒泡排序 | 稳定 | ||||
插入排序 | 稳定 | ||||
计数排序 | 稳定 | ||||
基数排序 | 稳定 | ||||
快速排序 | 不稳定 | ||||
归并排序 | 稳定 | ||||
堆排序 | 不稳定 | ||||
桶排序 | 稳定 | ||||
希尔排序 | 不稳定 | ||||
锦标赛排序 | 稳定 | ||||
稳定 |
1|8前缀和
定义
前缀和可以简单理解为「数列的前
一维前缀和
预处理递推式为:
查询
二维前缀和
预处理递推式为:
查询
树上前缀和
设
若是点权,
若是边权,
1|9差分
定义
差分是一种和前缀和相对的策略,可以当作是求和的逆运算。
这种策略的定义是令
性质
-
的值是 的前缀和,即 。 -
计算
的前缀和 。
它可以维护多次对序列的一个区间加上一个数,并在最后询问某一位的数或是多次询问某一位的数。注意修改操作一定要在查询操作之前。
树上差分
树上差分可以理解为对树上的某一段路径进行差分操作,这里的路径可以类比一维数组的区间进行理解。例如在对树上的一些路径进行频繁操作,并且询问某条边或者某个点在经过操作后的值的时候,就可以运用树上差分思想了。
树上差分通常会结合树基础和最近公共祖先来进行考察。树上差分又分为点差分与边差分,在实现上会稍有不同。
点差分
举例:对树上的一些路径
对于一次
其中
可以认为公式中的前两条是对蓝色方框内的路径进行操作,后两条是对红色方框内的路径进行操作。不妨令
边差分
若是对路径中的边进行访问,就需要采用边差分策略了,使用以下公式:
由于在边上直接进行差分比较困难,所以将本来应当累加到红色边上的值向下移动到附近的点里,那么操作起来也就方便了。对于公式,有了点差分的理解基础后也不难推导,同样是对两段区间进行差分。
1|10二分
时间复杂度
二分查找的最优时间复杂度为
空间复杂度
迭代版本的二分查找的空间复杂度为
递归(无尾调用消除)版本的二分查找的空间复杂度为
三分法
三分法衍生自二分法,三分法求单峰函数的峰值。
算法流程:
设当前搜索域为
若满足
若满足
若满足
实数三分
整数三分
算法优化:
考虑计算时间复杂度,由于每次搜索域缩减到
因此时间复杂度为
我们完全可以将
在单峰性/单谷性能够证明的题目中,三分法都能高效地适配。
分数规划
分数规划通常描述为下列问题:每个物品有两个属性
经典的例子有最优比率环、最优比率生成树等等。
分数规划可以用二分法来解决。
1|11倍增
倍增在很多算法中均有运用,其中最常用的是
1|12构造
构造题是比赛中常见的一类题型。
从形式上来看,问题的答案往往具有某种规律性,使得在问题规模迅速增大的时候,仍然有机会比较容易地得到答案。
这要求解题时要思考问题规模增长对答案的影响,这种影响是否可以推广。
例如,在设计动态规划方法的时候,要考虑从一个状态到后继状态的转移会造成什么影响。
特点
构造题一个很显著的特点就是高自由度,也就是说一道题的构造方式可能有很多种,但是会有一种较为简单的构造方式满足题意。
看起来是放宽了要求,让题目变的简单了,但很多时候,正是这种高自由度导致题目没有明确思路而无从下手。
构造题另一个特点就是形式灵活,变化多样。并不存在一个通用解法或套路可以解决所有构造题,甚至很难找出解题思路的共性。
例题
例一:
构造一组
解题思路:
显然
至于构造思路是怎么产生的,大概就是观察样例加上一点点数感了吧。此题对于数学直觉较强的人来说并不难。
例二:
解题思路:
对于
当
当
首先,我们可以发现
然后,我们考虑构造出整个序列的方式:
考虑通过构造前缀和序列的方式来获得原数列,可以发现前缀和序列两两之间的差在模意义下不能相等,因为前缀和序列的差分序列对应着原来的排列。
因此我们尝试以前缀和数列在模意义下为
这样的形式来构造这个序列,不难发现它完美地满足所有限制条件。
对于
当
当
先考虑什么时候有解:
显然,当
我们考虑如何构造这个数列:
和
我们发现这些数均为
例三:
给定一个整数
- 这是一个简单连通图。
- 存在一个整数
使得对于任意节点,与其相邻节点的下标和为 。
保证输入数据有解。
解题思路:
通过分析
构造一个完全
如果
如果
这样构造出的图在
此题得解。
2|0搜索
2|1简介
搜索,也就是对状态空间进行枚举,通过穷尽所有的可能来找到最优解,或者统计合法解的个数。
搜索有很多优化方式,如减小状态空间,更改搜索顺序,剪枝等。
搜索是一些高级算法的基础,在
习题
2|2DFS
先举一个例子:
把正整数
这个问题很简单,直接三层循环暴力就能解决:
但如果是把正整数
这时就要用到递归搜索了即
该类搜索算法的特点在于,将要搜索的目标分成若干「层」,每层基于前几层的状态进行决策,直到达到目标状态。
考虑上述问题,即将正整数
设一组方案将正整数
我们将问题分层,第
为了记录方案,我们用 arr 数组,第 i 项表示
2|3BFS
2|4Meet in the middle
算法的基本思路就是从状态图上的起点和终点同时开始
如果发现搜索的两端相遇了,那么可以认为是获得了可行解。
双向
性质
暴力搜索的复杂度往往是指数级的,而改用
2|5启发式搜索
定义
启发式搜索(英文:
启发式函数的作用是基于已有的信息对搜索的每一个分支选择都做估价,进而选择分支。简单来说,启发式搜索就是对取和不取都做分析,从中选取更优解或删去无效解。
例题
题目大意:
有
解题思路:
我们写一个估价函数
估价函数
我们在取的时候判断一下是不是超过了规定体积(可行性剪枝);在不取的时候判断一下不取这个时,剩下的药所有的价值 + 现有的价值是否大于目前找到的最优解(最优性剪枝)。
示例代码:
2|6A*
定义
过程
定义起点
如果
上述条件下,如果
当
例题
八数码。
题目大意:
在
解题思路:
容易发现
参考代码:
按顺序求一个有向图上从结点
解题思路:
很容易发现,这个问题很容易转化成用
初始状态为处于结点
就这样,我们在预处理的时候反向建图,计算出结点
由于设计的距离函数和估价函数,每个状态需要存储两个参数,当前结点
我们可以在此基础上加一点小优化:由于只需要求出第
参考代码:
注:对于
参考资料与注释
__EOF__

本文链接:https://www.cnblogs.com/So-noSlack/p/18321005.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
本文来自博客园,作者:So_noSlack,转载请注明原文链接:https://www.cnblogs.com/So-noSlack/p/18321005
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
2023-07-24 第十三节 小组学习
2023-07-24 AT_abc246_d 题解
2023-07-24 AT_abc215_d 题解
2023-07-24 AT_abc218_d 题解
2023-07-24 AT_abc180_d 题解
2023-07-24 第十二节 动态规划 - 4