0x0110 贪心思想
我觉得,我把草纸上的东西重新打一遍是一个很傻逼的事情。
0x0111 引入
生活中,用到贪心的地方有很多。举一个例子:买菜的时候,我们一般会“货比三家”,选择性价比最高的。如果还没买够,这家店就卖光了(虽然这种情况一般不会发生),那么就去买性价比次高的,以此类推。抽象成代码即为:
1 | void buy(int need, int money) |
此例中,我们使用了“取最大值”的贪心思想。
0x0112 常见思想
- 取最大/优值
如上。利用一些 DS 维护当前最大值,并每次取最大值。 - 取最远/近点
包括一些区间贪心和一些计算几何题,比如最小区间覆盖。此类题一般要取能取的范围内最远或最近的点,本质上仍然是取最优值思想。 - 取代价最大/小
若所有物品最终都必须选择,而选择物品的顺序会影响物品的花费(例如越晚选择花费越大),则可以考虑贪心地优先选花费增长快的物品。 - 权值计算
有时候取最大值的对象并不明确,可能需要推式子并化简。例题:保护花朵。
0x0120 适用范围
0x0121 最优子结构
能够使用贪心算法的问题一般都具有“最优子结构”。其含义为:问题能够分解成子问题来解决,子问题的最优解能递推到最终问题的最优解。(部分来源:OI-Wiki)
- 问题能够分解成子问题来解决
例如,一个序列上的问题,可以先考虑这个序列的一个子段的答案,并扩展到整个区间。 - 子问题的最优解能递推到最终问题的最优解
如果一个子问题的最优解一定是全局最优解的一部分,就可以使用贪心。否则,可以考虑求出所有子问题的最优解,并选这些解中最优的扩展,本质上是动态规划的基本思想。
0x0122 常见证明方式
- 反证法
如果交换方案中某两个元素不会使答案变得更好,则目前解已经是最优解了。不排除会有“局部最优解”出现,但如果问题具有“最优子结构”就没有这样的情况产生。 - 归纳法
由最小的子问题即“边界条件”一步步归纳,最终得出结论。
0x0130 常利用的 STL、算法与 DS
0x0131 取最优
贪心在选择最优解的过程中,如果不需要中途改动(最优解是静态的),则可以考虑先排序再取用。
一般常用 std::sort(begin, end[, compare = less<_Tp>])
排序,注意其不具有稳定性。为使 sort
排序具有稳定性,可以考虑将唯一编号与权值同时放入一个结构体,并以编号为第二关键字排序。另一种解决方案是 std::stable_sort(begin, end[, compare = less<_Tp>])
,其在无多余栈空间时时间复杂度有可能退化为 $O(n \log^2 n)$。其余情况下,两个函数的时间复杂度均为 $O(n \log n)$。
如果有改动(最优解可能是动态的),则需要利用数据结构维护最大值,较简单的问题可以使用堆。
STL 中,有 std::priority_queue<_Tp>
这一包装好的容器,可以用于简便地实现堆。默认行为下,此容器是大根的。
0x0132 维护区间修改与/或区间查询
部分题目中,需要支持区间处理信息。
以下部分内容可在数据结构专题中详细学习。
树状数组 Binary Indexed Tree
正常写法的树状数组,原生支持单点加、前缀求和。若调换修改和查询的写法,可以支持后缀求和。
树状数组一般只能支持具有可减性的操作。利用操作的可减性,可以通过前缀和得知区间和。
特别地,利用差分,可以实现区间修改、单点查询。
复杂度:$O(n \log n)-O(\log n)$
线段树 SeGment Tree
其常数较树状数组大,但其结构决定了其可以解决更多区间问题,包括大部分具有可加性的问题。可以实现所有树状数组能做的操作。
复杂度:$O(n \log n)-O(\log n)$
平衡树
包括 Splay、Treap、FHQ-Treap、文艺平衡树 等等。平衡树一般用于解决与排名相关的问题。
复杂度:$O(n \log n)-O(\log n)$
树套树
较常用的有树状数组套线段树、线段树套线段树、线段树套平衡树。由于树套树内节点较多,而用得到的相对少,所以树套树的所有线段树一般都动态开点。可以解决二维区间或动态区间排名等问题。
复杂度:$O(n \log n)-O(\log^2 n)$,视问题而有所不同。
0x0133 树上贪心
这部分包括一些较快的 LCA 求法,以及树链剖分、LCT、ETT 等较难数据结构。其中较简单的倍增 LCA、树链剖分在提高篇里就会讲到。
0x0140 例题
提高篇的这几道题也没什么说的……
0x0143 喷水装置
涉及到了一点点计算几何。考虑一个喷水装置的喷水区间是可以用勾股定理求的,因此求一下。注意用 double
存储。特别地,当一个喷水装置无法覆盖到区间时,将其左右端点均设为其位置,或者直接忽略。
0x0149 家庭作业
前置:E 智力大冲浪。
考虑 E 题的复杂度瓶颈在于枚举可选位置,这一步是 $O(n)$ 的,本题难以承受。
有两种做法,第一种是将 vis
用链表实现,这样可以 $O(1)$ 查找;第二种是利用 bitset
的小常数及其方法 _Find_next
查询,也可以卡过去,并且跑的很快。
0x014a 钓鱼
也可以考虑使用动态规划。