贪心百法

0x00 前言

这是 zlr 在 2025/5/5 日统计的贪心常用方法,勿喷。

0x01 用两个元素的交换来推出贪心方法(邻项交换法)

这个方法适用于解决顺序问题或者安排问题,通过先解决小问题来总结出规律,从而解决出这个题目。

例题:P1080 [NOIP 2012 提高组] 国王游戏

这个题目就是典型的邻项交换法推贪心。

1.假设

我们先假设国王后面只有两个大臣,而且假设国王拿着的是 \(a_0,b_0\),第一个大臣拿的是 \(a_1,b_1\),第二个大臣拿的是 \(a_2,b_2\),我们有两种顺序。

  • 第一个大臣在前面,那么第一个大臣得到的奖金就是 \(\frac{a_0}{b_1}\),第二个大臣拿到的奖金就是 \(\frac{a_0\times a_1}{b_2}\),最高的奖赏就是 \(\max{(\frac{a_0}{b_1},\frac{a_0\times a_1}{b_2})}\)。

  • 第二个大臣在前面,那么第二个大臣得到的奖金就是 \(\frac{a_0}{b_2}\),第一个大臣拿到的奖金就是 \(\frac{a_0\times a_2}{b_1}\),最高的奖赏就是 \(\max{(\frac{a_0}{b_2},\frac{a_0\times a_2}{b_1})}\)。

2.推导

现在我们就要来比较最高的奖赏,即比较 \(\max{(\frac{a_0}{b_2},\frac{a_0\times a_2}{b_1})}\) 和 \(\max{(\frac{a_0}{b_1},\frac{a_0\times a_1}{b_2})}\) 谁更小。

我们发现 \(\frac{a_0}{b_1}\) 一定小于等于 \(\frac{a_0\times a_2}{b_1}\)(因为 \(a_2\) 是大于 0 的整数,即 \(a_2 \ge 1\),不会变小),同理 \(\frac{a_0}{b_2}\) 一定小于等于 \(\frac{a_0\times a_1}{b_2}\)。所以我们可以将式 \([a]\) 转化为求

\[\min{(\frac{a_0\times a_1}{b_2},\frac{a_0\times a_2}{b_1})[a]} \]

的答案。

我们把 \(\frac{a_0\times a_1}{b_2}\) 和 \(\frac{a_0\times a_2}{b_1}\) 同时除 \(a_0\),在乘上 \(b_1\times b_2\),就把这两个式子化简为 \(b_1\times a_1\) 和 \(b_2\times a_2\)。发现什么没有?我们把结果的大小比较转化为了大臣手上数的乘积比较,如果 \(a_1\times b_1\) 小于 \(a_2\times b_2\),那么就把 1 号大臣排在前面,否则排在后面。

例题:P1248 加工生产调度

1.假设

我们假设有两个产品 \(x\) 和 \(y\),\(x\) 产品在 A 车间加工时间是 \(a_x\),同理,其他的分别为 \(b_x\)、\(a_y\)、\(b_y\)。

  • \(x\) 产品先做,那么 \(x\) 产品所有的时间就是 \(a_x+b_x\),由于 \(y\) 产品要等到 \(x\) 产品加工完才行,那么 \(y\) 产品在 A 车间加工就需要 \(a_x+a_y\) 的时间(\(a_x\) 的等待时间),而此时 \(x\) 产品在 B 车间加工了 \(a_y\) 的时间,还需要 \(\max{(b_x-a_y,0)}\) 的时间加工,所以 \(y\) 产品在 B 车间需要 \(\max{(b_x-a_y,0)}+b_y\) 的时间加工。由于 \(y\) 产品肯定是等到 \(x\) 产品加工后才结束,所以花费的总时间是 \(a_x+a_y+\max{(b_x-a_y,0)}+b_y\) 的时间。

  • 同理 \(y\) 产品先做就需要花费 \(a_x+a_y+\max{(b_y-a_x,0)}+b_x\) 的时间。

2.推导

现在我们需要比较 \(x\) 产品先做和 \(y\) 产品先做的时间,即求出

\[\min(a_x+a_y+\max(b_x-a_y,0)+b_y,a_x+a_y+\max(b_y-a_x,0)+b_x)[b] \]

我们把 \([b]\) 左右两个式子同时约一个 \(a_x+a_y\),这个式子就变成了这个样子:

\[\min(\max(b_x-a_y,0)+b_y,\max(b_y-a_x,0)+b_x)[c] \]

3.优化 max

我们发现 \(\max(a-b,0)\) 就等于 左右两式同时加 \(b\),再 \(\max\) 外面减去 \(b\),即等于 \(\max(a,b)-b\)。

\[\max(a-b,0)=\max(a,b)-b[d] \]

我们把 \([c]\) 进行如下化简:

\[\max(\max(b_x-a_y,0)+b_y,\max(b_y-a_x,0)+b_x \]

\[=\max(\max(b_x,a_y)-a_y+b_y,\max(b_y,a_x)-a_x+b_x) \]

\[=\max(\max(b_x,a_y)-a_y-b_x,max(b_y,a_x)-a_x-b_y)[e] \]

请注意,上面的最后一个式子就相当于第二个式子左右两边同时减了一个 \(b_y+b_x\),是没有问题的。

由于 \([d]\) 式,我们可以知道,\(\max(b_x,a_y)-a_y-b_x=max(-a_y,-b_x)\),同理,\(\max(b_y,a_x)-a_x-b_y)=\max(-a_x,-b_y)\)。

所以 \([e]\) 式就是 \(\max(\max(-a_y,-b_x),\max(-a_x,-b_y))\),将内层的 \(\max\) 去掉,变成 \(\max(-a_x,-a_y,-b_x,-b_y)\)。

由于 \(\max(-a,-b)=-\min(a,b)\),所以上式可以化简为 \(-\min(a_x,a_y,b_x,b_y)\)

所以最终的排序方法就是如果 \(\min(a_x,a_y)\) 小于 \(\min(b_x,b_y)\),就将 \(x\) 放在前面。

例题:P1842 奶牛玩杂技

1.假设

假设有两头牛 \(a\) 和 \(b\),那么它们的体重分别是 \(w_a\) 和 \(w_b\),力量是 \(s_a\) 和 \(s_b\)。

如果现在只有这两头牛,那么:

  • \(a\) 在上面,那么 \(a\) 的压扁指数为 \(-s_a\),\(b\) 的压扁指数为 \(w_a-s_b\),总压扁指数为 \(\max(-s_a,w_a-s_b)\)。(注意这里不能直接省去 \(-s_a\),虽然它为负数,但当 \(w_a-s_b<-s_a\) 时就会取 \(-s_a\)。)

  • \(b\) 在上面,那么同理,总压扁指数为 \(\max(-s_b,w_b-s_a\))。

2.进一步的假设

如果我们要使 \(a\) 在上面,那么我们该怎么做呢?

我们需要满足 \(\max(-s_a,w_a-s_b) < \max(-s_b,w_b-s_a)\)。

易证 \(-s_a < w_b-s_a,-s_b < w_a-s_b\) 所以 \(\max(-s_a,w_a-s_b)\) 就可以变为 \(w_a-s_b\),同理 \(\max(-s_b,w_b-s_a)\) 变为 \(w_b-s_a\)。

因为我们要使 \(w_a-s_b < w_b-s_a\),简单移项便可以得到 \(w_a+s_a<w_b+s_b\)。

所以如果要使 \(a\) 在上面,必须要 \(w_a+s_a<w_b+s_b\)。

3.更进一步的总结

当邻项交换法推出来的式子比较复杂,无法直接化简时,我们可以从结果出发,如果要使 \(a\) 在 \(b\) 前面,那么 \(a\) 和 \(b\) 需要什么条件呢?我们把这个场景带入式子,就可以通过 \(a\) 在 \(b\) 前面这一个我们人为构造的条件来判断贪心策略。

0x02 区间类贪心(区间类问题)

这个方法适用于一系列的区间取舍问题,最基础的问题有:

  1. 区间选点(在 \(n\) 个区间放尽量少的点,使得每个区间都有点)
  2. 区间覆盖(选择尽量少的区间覆盖整个线段)
  3. 区间分组(将区间分成尽量少的组使得组内的区间两两互不相交)

区间类问题的常用方法是按右端点排序,方便解决区间取舍后对下一个区间的影响(如果不按右端点排序就有后效性了,不能用贪心解决)。

例题:P2255 [USACO14JAN] Recording the Moolympics S

同样,我们先按右端点排序,然后因为有两个节目可以同时录制,我们用 \(p_1\) 和 \(p_2\) 来代表两个节目的结束时间。

贪心策略:由于靠前的节目是结束时间更小的,所以结束后可以更快安排下一个节目,所以我们只需要按右端点排序后挨个判断节目是否可以即可。

0x03 数据结构优化贪心------并查集

在有些时候,贪心的复杂度为 \(n^2\),其中一个 \(n\) 的复杂度是寻找前面满足条件的元素所需的复杂度,所以我们可以用并查集优化。

例题:U213773 家庭作业

我们很容易推出贪心策略:先选得分大的,如果这个作业有时间就做,没时间直接舍去,但由于每次查找之前的时间都需要 \(n\),总复杂度为 \(n^2\),考虑并查集优化。

我们定义 \(f_i\) 为 \(i\) 天前的第一个 空闲时间,那么查找这个作业是否可以就是 \(find(x)\),复杂度为 \(1\)(路径压缩)。

例题:P1525 [NOIP 2010 提高组] 关押罪犯

很明显,我们需要让危害性较大的两个罪犯分到两个监狱里。我们用 \(f_i\) 表示第 \(i\) 个人和 \(f_i\) 个人放在一个监狱里,\(b_i\) 表示跟 \(i\) 仇恨度最大的人是谁。

很明显,如果现在 \(b_x\) 已经有值了,所以为了他们并不互相冲突,只能将 \(b_x\) 和 \(y\) 放在一个监狱里面,如果没有 \(b_x\),根据贪心策略,肯定将 \(x\) 和 \(y\) 放在两个监狱里。

0x04 数据结构优化贪心------优先队列

贪心中查找最大或最小值且两个元素在同一位置的查找范围一样 ,那么就可以用 \(\log n\) 的复杂度取代 \(n\) 的暴力枚举。

例题:[ABC407E] Most Valuable Parentheses

很明显,我们只要满足左括号的数量等于右括号的数量且在任何一个 \(1\sim i\) 的序列中的左括号都要大于右括号,所以我们只需要在前 \(i \times 2 - 1\) 个当中选择 \(i\) 个,可以证明保证从 \(2\times x - 1\) 到 \(2 \times (x + 1) - 1\) 会同时增加一个左括号和右括号。

所以我们可以用优先队列,从 \(1\) 枚举到 \(2\times n\),每次枚举到奇数位就将序列中最大的元素累加答案。