0%

贪心

贪心

贪心是一种思想,而不是一种具体的算法,没有固定的写法。

下面介绍几种贪心经典算法来引入贪心的思想。

贪心的熟练运用需要通过大量的做题自行提高。

贪心在算法竞赛中具有极广的应用,可以说 $50%$ 的题目都用到了贪心的思想。

区间选点:

给定 $n$ 个区间 $[l_i,r_i]$,选择最少的点,使得每个区间内至少包含一个选出的点。(只关心数量)

将区间按 $l_i$ 为第一关键字升序,$r_i$ 为第二关键字降序排序。

考虑排序后的 $n$ 个区间,从第一个区间开始,用一个点覆盖尽可能多的区间,即:$l_i\leq r_1$ 的区间。从第一个满足 $l_i>r_1$ 的区间开始,同样考虑。(实际上选的点就是 $r_1$)

证明 $1$:

设最优解为 $ans$,上述解法求出为 $res$。

易得:$ans\leq res$。

对于 $res$ 个点,每一个点都需要用至少一个区间去覆盖,同时任意两个区间之间不会相交。因为如果相交,那么在上述算法过程中会被算在前一个区间上,不符合算法过程。即:$ans\geq res$。

综上:$ans=res$。

证明 2:

对于 $n$ 个区间,初始没有任何区间被覆盖,但最终所有点都会被覆盖。因此不妨选择第一个区间 $[l_1,r_1]$ 作为第一个被选的区间,考虑哪些区间是通过在 $[l_1,r_1]$ 中选择一个点就能被覆盖的:和 $[l_1,r_1]$ 有交集的区间:

  • $l_i<l_1,r_i\in [l_1,r_1]$
  • $[l_i,r_i]\subset [l_1,r_1]$ 覆盖 $[l_i,r_i]$ 一定会覆盖 $[l_1,r_1]$,所以可以去掉 $[l_1,r_1]$。
  • $r_i>r_1,l_i\in[l_1,r_1]$

第一类和第三类不能仅仅通过一个点覆盖,需要两个点,而区间 $[l_1,r_1]$ 被划为那一个点,是无法确定的。

同时,覆盖点的数量只和区间有关,而和顺序无关。所以,将区间排序,有序后,由于 $[l_1,r_1]$ 是最靠左的,所以 $[l_1,r_1]$ 不会面对第一类还是第三类的抉择,一定是第三类。往后一样考虑即可。

最多不相交区间:

给定 $n$ 个区间 $[l_i,r_i]$,选择最多的区间,使得任意区间之间不相交。(只关心数量)

将区间按 $r_i$ 升序。

考虑排序后的 $n$ 个区间,从第一个区间开始,尽可能多地选择区间。$l_i>r_1$ 的区间。因为 $r_i\geq r_1,l_i\leq r_1$ 的区间和 $[l_1,r_1]$ 相交。

证明 1:

设最优解为 $ans$,上述解法求出为 $res$。

易得:$ans\geq res$。

对于已经求出来的 $res$ 个区间,若存在第 $res+1$ 个区间满足不与其它任意区间相交,设这个区间为 $[L,R]$,则一定满足 $\exist\ r_i<L\land R<l_{i+1}$。而如果存在这样的区间 $[L,R]$ 在上述算法中一定是会被考虑到的,所以不存在这样的区间,即:$ans<=res$。

综上:$ans=res$。

证明 2:

考虑选完的最终 $m$ 个区间,满足:$l$ 和 $r$ 分别严格单增。

同样地,考虑第一个区间,第一个区间的 $r$ 一定是最小的。若不是最小的,则换成 $r$ 最小的,并不会使答案变劣。后面的也同理,不会让答案变劣。因此将区间按 r 升序,这样得到的区间满足每次能取到的 $r$ 一定是最小的,只要考虑 $l$ 即可。

addition:

其实可以发现:“区间选最少点”和“最多不相交区间”本质是相同的。(即:同一份代码即可以解决前者也可以解决后者)

因为区间选点中的点所覆盖的区间都是相交的,选择了其中之一就不能再选择其它区间。所以“一个点对应了一个区间”。选出的区间对应于选出的点。

区间分组:

给定 $n$ 个区间 $[l_i,r_i]$,把区间分成最少的组,使得每组内的任意区间之间不相交。(只关心数量)

结合前文,在区间选点中,一个点对应的区间不能在一个组内。所以一个点对应的每个区间都对应了一个组。考虑这若干个组,在下一个点的若干个区间中,$l$ 最小的那个可以放进这若干个组中的任何一个,而放进 $r$ 最小的那个组最优。因为小的 $l$ 能放的组,大的也能放;而小的 $l$ 不能放的组当然也放不了。即:反之不优。若不能放到之前已有的组中,则新开一个组。

所以从前往后考虑每一个点,用优先队列维护每一个点对应的区间的 $r$ 的有序性。

最后的一个组对应一个优先队列。

区间覆盖:

给定 $n$ 个区间 $[l_i,r_i]$,选择最少的区间覆盖 $[s,t]$。(只关心数量)

考虑第一个,覆盖 $[s,h]$(前缀)的一个区间。一定是满足 $l\leq s$ 的 $r$ 最大的一个区间。那么后续对 $(h,t]$ 同样处理就好了。

所以对 $n$ 个区间按 $l_i$ 为第一关键字升序,$r_i$ 为第二关键字升序排序。每次找到 $l_i\leq s$ 的最大 $r_i$ 即可。

合并果子:

Huffman 树。

给定 $n$ 堆果子,每次可以花费两堆果子数目之和的代价合并两堆(会形成新的一堆)。求合并成 $1$ 堆的最小代价。

假设仅考虑选择两堆合并,那一定是果子数目最少的两堆。所以从直觉上讲,每次选择数量最少的两堆合并即可。

但是正向考虑每次操作不易理解,可以从“贡献”角度入手。考虑每一堆果子(初始的 $n$ 堆)被会被算几次。

考虑将果子合并的过程抽象成一棵二叉树:若合并 $x$ 号堆和 $y$ 号堆果子并形成一个 $z$ 号堆果子,则将 $z$ 号结点的左儿子设为 $x$,右儿子设为 $y$。

那么最后的代价就是:$\sum count_{叶子}\times dep_{叶子}$。

引理:数量最少的两堆果子堆一定是深度最大的。

显然。

如果深度最小的两堆不是最少的两堆,把最少的换过去答案一定更优。

而两个果子堆合并后形成了新的果子堆,这新的果子在后续的操作独立,后续操作同理即可。

根据数学归纳法,可得最后的 $\sum count_{叶子}\times dep_{叶子}$ 为最小值。

这一棵树就是 Huffman 树,也称最优二叉树。

排队接水:

给定 $n$ 个人的接水时间 $t_i$,安排排队顺序使得所有人等待时间之和最小。

假设排队顺序为排列 $p_n$,令 $T_i=\sum\limits_{j=1}^i t_j$。求的即为:$\sum\limits_{i=1}^nT_i$ 的最小值。同样考虑贡献,将式子变为:$\sum\limits_{i=1}^n(n-i+1)t_{p_i}$。

$a_i=n-i+1$ 为一个递减序列,$t_{P_i}$ 递增时 $\sum\limits_{i=1}^n(n-i+1)t_{p_i}$ 最小。

这就是排序不等式:

  • 对于确定的两个序列 $a_n,b_n$,满足 $a_{p_i}$ 递增,$b_{q_i}$ 递减时 $\sum a_i\times b_i$ 取得最小。

证明:

对于两个乱序序列 $a_n,b_n$,不妨考虑让 $a_n$ 升序。

若 $b_1$ 此时不是最大值,不妨令 $b_j>b_1$。将 $b_j$ 和 $b_i$ 交换,做差,得新序列和原序列相差 $a_1\times b_j+a_j\times b_1-a_1\times b_1-a_j\times b_j=a_1\times(b_j-b_1)+a_j\times(b_1-b_j)=(b_j-b_1)(a_1-a_j)\leq0$

所以可以不断换最更大的,直到把最大值放到 $b_1$。对于后续 $2-n$ 同理。

货仓选址:

给定 $n$ 个商店的坐标 $a_i$,选定一个货仓 $x$,使得 $\sum\limits_{i=1}^n|a_i-x|$ 最小。

不妨将 $a_i$ 升序,货仓的坐标即为 $a_{\lceil\frac n2\rceil}$。

引理 $1$:货仓的坐标为某个商店的坐标。

反证,若 $\forall a_i\neq x$,则 ${\exist} a_i<x\land a_{i+1}>x$,那么对于 $j<i$ 和 $j>{i+1}$ 答案不变。而对于 $a_i$ 和 $a_{i+1}$,货仓位置设置在哪事实上没有影响。所以不妨设为 $a_i$。因为这样就将待选位置缩小到 $n$ 个 $a_i$ 中。

此时,考虑特殊情况。

引理 $2$:$\sum\limits_{i=1}^n|a_i-a_1|>\sum\limits_{i=1}^n|a_i-a_2|(n>2)$。

展开:$\sum\limits_{i=2}^na_i>\sum\limits_{i=3}^na_i-a_1$,易证。

所以,$x\neq a_1$。而对于 $x=a_2$ 的情况,可以和 $x=a_1$ 的情况同样考虑(在之后的过程中 $|a_1-x|+|a_n-x|=a_n-a_1$ 为定值,所以可以消去)。

当 $n=2$ 时,$x=a_1$ 和 $x=a_2$ 结果相等;或 $n=1$ 时,$x=a_1$。(取决于 $n$ 的奇偶性,因为每次去掉 $a_1$ 和 $a_n$ 两个位置)

国王游戏:

国王有 $n$ 个大臣,每个大臣左手有 $a_i$,右手有 $b_i$。每个大臣的得分是排在他前面的人的左手之积除以他的右手。国王的左手是 $a_0$,右手是 $b_0$,求一种排队顺序使得得分最大值最小。

考虑两个相邻的人谁在前面更优。$i$ 的得分:$\dfrac{\prod\limits_{j=1}^{i-1}a_j}{b_i}$,$i+1$ 的得分:$\dfrac{\prod\limits_{j=1}^{i}a_j}{b_{i+1}}$ 。

两者消去公共部分后分别为 $\dfrac{1}{b_i}$ 和 $\dfrac{a_i}{b_{i+1}}$。

如果交换,那么消去公共部分后分别为 $\dfrac{1}{b_{i+1}}$ 和 $\dfrac{a_{i+1}}{b_i}$。

注意到:$\dfrac{a_i}{b_{i+1}}>\dfrac{1}{b_{i+1}},\dfrac{1}{b_i}<\dfrac{a_{i+1}}{b_i}$。

若 $\dfrac{1}{b_i}<\dfrac{a_i}{b_{i+1}}$,即:$a_i\times b_i>b_{i+1}$,则交换后最大值只能是 $\dfrac{a_{i+1}}{b_i}$。

而如果交换更优,那么要满足 $\dfrac{a_i}{b_{i+1}}>\dfrac{a_{i+1}}{b_i}$,即:$a_i\times b_i>a_{i+1}\times b_{i+1}$。显然,满足这个条件的一定满足前置条件。

若 $\dfrac{1}{b_i}>\dfrac{a_i}{b_{i+1}}$,即:$a_i\times b_i<b_{i+1}$,则交换后的最大值就是 $\dfrac{a_{i+1}}{b_i}$ 且不更优。所以不需要交换。

所以将大臣们按照 $a_i\times b_i$ 的值升序排队即可。

为什么只要考虑相邻两个?

因为如果队伍不是按 $a_i\times b_i$ 有序的,那么就一定会有两个相邻的人满足 $a_i\times b_i>a_{i+1}\times b_{i+1}$,而调整它,就会更优。所以一定会调整至 $\forall a_{i}\times b_i<a_{i+1}\times b_{i+1}$。

反悔贪心:

在一般贪心中,通常为得出一个策略,按照策略即可直接得到答案。而有的时候,结合以往的结果去对修正当前的决策也是可以的,但是从宏观上看,还是“贪心”的。对于这种贪心,一般被称为反悔贪心(也有说法细分为反悔堆和反悔自动机)。

任务安排:

给定 $n$ 个任务,对于每个任务都有一个截止日期($\leq 10^9$)和完成价值。从第一天开始,每天可以选择一个任务完成。求能获得的最大价值。

对于两个不同截止日期的任务,一定是先完成截止早的,显然。

对于两个相同截止日期的任务,一定是先完成价值大的,显然。

所以先将任务按截止日期升序,但是时间范围过大,不允许枚举模拟。

考虑反悔贪心:

若在某一天发现一个价值很大没有选择的任务,但是任务安排已经超过这天了。这时,我们反悔了。所以要去掉一个已经选择的中最小的一个,替换成这个价值大即可。

所以用一个优先队列维护已经选择的任务, 由于任务总数不大,所以最终时间复杂度为:$O(n\log n)$。

使用堆维护类似的过程,也被部分人称作反悔堆。

种树:

共有 $n$ 个坑可以种树,且每个坑种树后能获利 $a_i$,但要求不能有两棵树相邻。求种 $k$ 棵树最大获利。

若无不能相邻的限制,则直接选取前 $k$ 大即可。

考虑有什么区别,不能相邻,每次有两种情况:

  • 在不相邻的位置中,选择一个最大值,贡献即为 $a_j$。
  • 选择一个相邻的位置但是要把当前这棵树给挖掉,所以贡献为 $a_{i+1}-a_i$ 或者 $a_{i-1}-a_i$。可是如果 $a_{i+1}-a_i>0$ 或者 $a_{i-1}-a_i>0$ 的话,那么 $a_{i+1}$ 或者 $a_{i-1}$ 会在之前就被考虑,所以不能选择左右之一,只能是 $a_{i+1}+a_{i-1}-a_i>0$ 即相邻的同时选。

所以,每次贪心在未被选择的 $a_1,..,a_{i-2},a_{i+2},…,a_n$ 和 $a_{i+1}+a_{i-1}-a_i$ 中选择一个最大值。

若选择的是 $a_{i+1}+a_{i-1}-a_i$,那不能再选的是 $a_{i+2}$ 和 $a_{i-1}$。所以我们用一个新建节点来表示 $a_{i+1}+a_{i-1}-a_i$ 且用它来替代原本 $i$ 的位置,使用双向链表维护相邻元素,并标记相邻元素不能被选(如果同一个点被选三次,那么它本身的贡献会重新变成正的,此时便蕴含着反悔的含义)。

共选择 $k$ 次最大值。

上述过程也被部分人称之为反悔自动机。