一般来说,如果题目需要求一个最优解或者最小(大)花费之类的,而且除了暴力之外想不到什么好方法,那么就可能需要用贪心。
通常地,我们猜想一些步骤能不能直接使用贪心,然后再去证明这个贪心是对的。
有时候可能要多想几种贪心才能找到正确的那一种。
New Year Snowmen
Description
要堆起一个雪人,需要三个不同大小的雪球。现在有 \(n\) 个给定大小的雪球,问最多能堆起多少个雪人,并输出方案。
Solution
每次用数量最多的三个雪球是最优的。可以用一个单调队列,每次取出最大的三个数,再把余下的丢回队列。
Discounts
Description
超市打折,如果购物车里有至少一个凳子,则可半价购买购物车里最便宜的一个物品。现在你要购买 \(n\) 个物品,其中一些是凳子。你有 \(k\) 个购物车,求一个最优的购买方案,使得花费的价格最少。
Solution
每个购物车的凳子只能优惠价格低于它的,但是优惠价格减小了还不如不优惠。所以将价格最大的 \(k-1\) 个凳子放入前 \(k - 1\) 个购物车,余下的购物车放全部的其他物品。
叠罗汉
Description
有 \(n\) 个罗汉,每个罗汉有重量 \(w\) 和力量 \(s\)。定义一个罗汉的危险值为他上面所有罗汉的重量之和减去他的力量。安排一个顺序使得危险值最大的罗汉的危险值最小。
Solution
如果已经有一个排列,可以对这个排列相邻两个罗汉 \(i\) 和 \(j\) 进行调整,\(i\) 和 \(j\) 交换不会影响他们上下的人,所以考虑 \(i\) 和 \(j\) 哪个在上面最优,因为要让最大值最小,分类讨论两人的危险值或感性猜测,可发现让两个罗汉中 \(w+s\) 更小的在上面。最后调整的结果就是将所有罗汉按 \(w + s\) 从小到大排序。
建筑抢修
Description
基地里有 \(n\) 个建筑设施受到了严重的损伤,但只有 一个修理工人。修复一个建筑都需要 \(t_i\) 的时间,工人一次只能修一个。如果某个建筑在 \(d_i\) 时间之内没有修理完毕 ,这个建筑就报废了。你的任务是制订一个合理的维修顺序,以抢修尽可能多的建筑。
Solution
若第 \(i\) 号出现时间不足,那么前 \(i\) 个建筑中最多修复 \(i-1\) 个建筑。必然选择 \(t_i\) 较小的前 \(i-1\) 个建筑,给后面的修复留下更多的时间。按 \(d\) 从小到大排序,中途用堆维护 \(t_{max}\)。如果能修第 \(i\) 个建筑,那么就修,不然从堆中取出所需时间 \(t\) 最大的,如果 \(t_{max} > t_i\) 就不修 \(max\) 而修 \(i\)。
扁鹊再世蔡徐坤
Description
因为版权原因,不放题面。
Solution
如果某个方案中存在一个人不能被镇静,把他去掉可以让其他人分到更多的量,答案只会更优。令 \(s_i\) 为每个人满意的情况下能够接受的最大的分子 。如果从大到小枚举 \(s_i\) 最小的人,可以选的人越来越多。但因为 \(a_i\) 之和应小于 \(s_i\),所以选择 \(a_i\) 最小的一些人,这个过程可以用堆来维护,将枚举的人加入优先队列,再从队列中不断弹出 \(a_i\) 最大的人直到 \(a_i\) 之和 \(\leq s_i\)。
密码锁
Description
给定 \(a[1 \sim n], a_i \in [0,m-1]\),每次操作可以在模 \(m\) 意义下对任意一个区间 \([l,r]\) 整体 \(+1\) 或 \(-1\),求最少几次操作可以使所有数字变成0。
Solution
在 \(n\) 个数前后补上 \(0\),进行在模意义下的差分,有差分数组 \(b\),在模意义下的区间加减转为选择一位 \(+1\),另一位 \(-1\)。要让 \(b\) 全为 \(0\) 或 \(m\),先对 \(b\) 从小到大排序。枚举找到一个分界点 \(mid\),让 \(mid\) 左边的数尽可能接近 \(0\),\(mid\) 右边的数尽可能接近 \(m\) ,用 \(mid\) 左边的数 \(+1\),右边的数 \(-1\) 互相抵消**。如果有多余的,可以贪心地控制多余的数尽可能的接近前缀 \(0\) 或后缀 \(0\),就可以自己消自己了。
\[
ans = max\{ \sum_{i = 1} ^ {mid} b_i, \sum_{i = mid + 1} ^ {n} m - b_i\}
\]
这个式子可以用前缀和预处理搞定。
删数问题
Description
给定一个大正整数 \(S\) 你需要去掉其中任意 \(n\) 位数字。剩下的数字按原次序组成一个新的正整数\(S’\)。对给定的 \(n\) 和 \(S\),寻找一种方案使得剩下的数字组成的新数 \(S’\) 最小。
Solution
要让高位尽可能小。先在数字最后一位补一个 \(-1\),输出时忽略掉 \(-1\)。然后从高位向低位枚举,如果在某一个位置数字是递减的,那么将开始递减的数字删掉。如 \(S = 1785438,n = 4\),删除如下
\(178543 \{ 8 \}\) \(\quad\) \(17543\{ 7 \}\) \(\quad\) \(1543\{ 5 \}\) \(\quad\) \(143\{ 4 \}\) \(\quad\) \(13\)
取数游戏
Description
给出 \(n\) 个正整数,你需要把它们连接成一排,组成一个最大的多位整数。
Solution
重载 \(<\) 的定义,如果 \(a\) 与 \(b\) 拼接比 \(b\) 与 \(a\) 拼接大,就认为 \(a > b\)。按照重载的小于号从小到大排序即可。如 \(12\) 和 \(121\),虽然数学中 \(121 > 12\),但是因为 \(12121 > 12112\),所以 \(12 > 121\)。
Main Sequence
Description
定义幸运数列:空序列是幸运序列。如果 \(S\) 为幸运数列,有一个正整数 \(k\),那么 \(\{k, S, -k\}\) 也是幸运序列。如果 \(S\) 和 \(T\) 都是幸运序列,那么 \(\{S+T\}\) 也是幸运序列。现在给你一个有 \(n\) 个元素的序列每个数的绝对值,同时有 \(m\) 个限制,每个限制为数列的第 \(i\) 个数一定为负数。请你求出这个幸运数列。
Solution
这道题就是一个括号匹配,\(k\) 可以看作是左括号,\(-k\) 可以看作是右括号。因为 \(m\) 个右括号已经确定了,所以我们从后往前扫这个序列。相似的,我们用栈来维护右括号,对于每个数有两种情况,如果他是 \(m\) 个数之一或者不能与栈顶的右括号匹配,那么让他加入未匹配的右括号栈中,否则我们将栈顶的数退栈,表示匹配成功。如果将序列扫完栈不为空,则匹配失败。
兔子和樱花
Description
给定一颗 \(n\) 的点的有根树,每个点 \(x\) 上面有一些樱花 \(a_x\),设 \(fa_x\) 为 \(x\) 父亲,\(son_{fa_x}\)为父亲的儿子数。现在可以删掉一些节点,被删除的节点 \(x\) 的樱花会累加到 \(fa_x\) 上,\(x\) 的子节点也会接到 \(fa_x\) 上。要求删除完 \(x\) 后,\(a_{fa_x} + son_{fa_x} < m\)。问最多能删多少个点。
Solution
从下向上进行树形 dp,对于每个点,删除的代价为 \(c_i + son _i\),我们对于每个点尽可能多地删除它代价最小的儿子。
刺客
Description
你是一个刺客,有把耐久度为 \(M\) 的刀,你要杀死 \(N\) 个敌人,其中杀死第 \(i\) 个敌人需要消耗 \(A_i\) 点耐久度,但可以得到一把能杀死 \(B_i\) 个敌人的武器(不消耗自己的耐久度)。问最多可以杀死多少个敌人。以及在这个前提下,最少需要消耗的耐久度。
Solution
- 先杀一个 \(B_i > 0\) 中 \(A_i\) 最小的人,借刀杀掉所有 \(B_i > 0\) 的人,如果还有刀,就杀掉剩下 \(A_i\) 最大的人。最后用自己的刀把 \(A_i\) 尽可能小的一些人杀掉。但是还存在一种情况,对于 \(B_i > 0\) 的人,不用敌人的刀杀死他而用自己的刀就有可能杀掉更多的人。
- 可能 \(B_i > 0\) 的人 \(A_i\) 都非常大,所以可能只能杀 \(A_i\) 尽可能小的一些人。
打游戏
Description
因为版权原因,不放题面。
Solution
先将 \(n\) 个怪从小到大排序,然后依次打。如果场上最小怪的生命值为 \(1\) 时能放群攻放群攻,如果怪的个数 \(>2\) 能放群攻放群攻,如果只有两个怪且他们生命值 \(\ge 2\) 时从小到打能放重击放重击。
DFS Spanning Tree
Description
给出一个没有自环的有向图。这个图的前 \(n-1\) 条边构成这个图的一个以节点 \(1\) 为根节点的 dfs 树。T-Simple 环的定义是:至多有一条边不在这棵树上的环。至少在图上选中多少条边。才使得每个 T-simple 环都至少有一条边被选中。
Solution
题目给出了是 dfs 序的树,可以发现,除了树上的边就只有返祖边。每条非树边都相当于在 dfs 生成树上划定了一条深度单调的链,问题转化为最少染色多少条边,可以使每条链上都至少有一条边被染色。
先考虑一个不在树上的子问题, 有一个序列,现在有 \(n\) 个限制条件。第 \(i\)个限制是,\(L_i\) 到 \(R_i\) 的这段区间内至少有一个位置被打标记。问至少要打几个标记。可以从左往右扫,对于每个没有标记的限制条件 \(x\) 就贪心地在 \(R_x\) 打一个标记。
那么这个问题就是把数列改成了一棵树,把限制放在树上。按树的 dfs 序从前往后像序列上那样处理就可以了。
乐曲创作
Description
给你一个 \(n\) 个数的排列 \(a[1 \sim n]\) ,设它有 \(m\) 个逆序对。现在要求找出字典序大于原序列最小的排列,使它逆序对的个数也等于 \(m\)。
Solution
令 \(b\) 为构造的数组,那么有一个 \(p\) 满足 \(a_{1 \sim p - 1} = b_{1 \sim p - 1}\) 且 \(a_p \leq b_p\)。最优的 \(b\) 一定是在 \(p\) 尽可能靠后的前提下,\(i \in b_{[p + 1,n]} \ a_{i}\) 在 \(a_{i-1}\) 尽可能小的前提下尽可能的小。
所以从后往前找到一个 \(p\),首先满足他后面有至少一个比它大的数。其次,我们把一个数交换成比它大的最小的数,会造成多的逆序对,如果 \([p+1,n]\) 的数按照从小到大排列,那么 \([1,n]\) 的逆序对个数最少,在最少的情况下,逆序对总数还 \(> m\),那么我们就只能放弃这个 \(p\) 去枚举之前的 \(p\)。否则,我们一定有策略让 \([1,n]\) 的逆序对总数 \(=m\)。可以证明,对于第二个限制,只要 \([p+1, n]\) 中有至少一个逆序对,那么一定有策略让 \([1,n]\) 的逆序对总数 \(=m\)。
确定 \(p\) 后,\(b_{[1,p - 1]}\) 与 \(a_{[1,p - 1]}\) 相同,\(b_p\) 为 \(a_{[p + 1, n]}\) 中 \(>a_p\) 的最小的数,接下来要从 \(p\) 往后做构造 \(b_{[p + 1,n]}\)。我们找到了 \(p\),那么已经搞定了 \(b\) 的字典序 \(> a\) 的问题和无论如何安排数逆序对总数都 \(> m\) 的问题 ,对于 \(b_{[p + 1,n]}\) 的构造,要让越往前的位置安排的数尽可能的小,如果我们在 \(i \in b_{[p + 1,n]} \ a_{i}\) 安排了一个 \(<a_i\) 的数,那么逆序对总数会减少。如果 \([i+1,n]\) 按照从大到小排列,那么 \([1,n]\) 的逆序对个数最多,在最多的情况下,逆序对总数还 \(<m\),那么我们要在位置 \(i\) 安排一个大一点的数。
Shop
Description
现在有 \(n\) 个数 \(a_i\) 。有三种操作,\(a_i = b\),\(a_i + b\) 与 \(a _i \times b\),其中 \(i\) 和 \(b\) 为给定参数。现在给你 \(m\) 以上个操作,你能从中最多选出 \(k\) 个并自行安排顺序,要求操作完后 \(n\) 个数的乘积最大。
Solution
可以发现先进行 \(a_i = b\) ,再进行 \(a_i + b\),最后进行 \(a_i \times b\) 是最优的。考虑将前两种操作转换成第三种。对于第一种操作通过 \(b - a_i\) 转成第二种,第二种可以通过 \(\frac{(a_i + b)}{a_i}\) 求出倍率。然后全部成为操作三了,从大到小排序求。这样做并不违背之前的规律,因为乘法的顺序和乘在哪个 \(a_i\) 上是没关系的。但是输出的时候一定要按照先赋值再加最后乘的顺序。
Color a tree
Description
给定一棵有 \(n\) 个节点的树,每个节点有一个权值 \(W_i\),你需要从前往后一个一个地把点安放进数组 \(a\) 中。\(a\) 需要满足若点 \(a_i\) 为 \(a_j\) 的父亲,则 \(i\) 必须小于 \(j\) 。一种安放方案的权值为 \(\sum_{i=1}^n W_{a[i]} \times i\),请找出权值最小的方案并输出最小权值。
Solution
如果没有父节点排在节点之前的限制,就可以按照权值从大到小排序的顺序选。虽然这道题要求父节点必须在所有子节点前面,但是也可以仿照没有限制的思路。对于一个权值极大的点,如果没有限制的话我们越早放这个点越好,但是有限制的话,就必须将这个点与他的父亲绑定在一起,一旦他的父亲放了,就紧挨着他的父亲放进去。
先不考虑如何绑定节点,而是考虑一个子问题,如果在保持这个紧挨着的绑定关系同时,其他节点不作限制,我们应该如何排列呢? 我们假设绑定在一起的两节点是 \(A\) 和 \(B\) 。现有另外一个节点 \(X\),我们观察两种排列 \(XAB\),\(ABX\) 对最终的计算结果有什么影响,假设第一个数的位置在序列中是 \(i\) ,这两个排列带来的权值分别是:
\[
Value_1 = W_X \times i+W_A \times (i+1) + W_B \times (i+2)
\]
\[
Value_2 = W_A \times i+W_B \times (i+1) + W_X \times (i+2)
\]
显然是谁的 \(Value\) 小就按照谁排序,如果我们用 \(Value_2 - Value_1 = 2 \times W_x - (W_A + W _B)\),如果式子结果 \(>0\),那么就选前者,否则选后者。进一步地,我们比较的是 \(\frac{W_A + W_B}{2}\) 与 \(\frac{W_x}{1}\)。发现这个东西,有点像平均数?尝试把结论推广到确定两个序列的关系上,找一找规律,发现真的就是平均数。也就是说,设一个序列 \(W_1\) 有 \(N\) 个节点,另一个序列 \(W_2\) 的序列有 \(M\) 个节点,我们可以比较 \(\frac{ \sum_{i=1}^N W_{1_i}}{N}\) 与 \(\frac{ \sum_{i=1}^M W_{2_i}}{M}\)。我们将 \(W_1\) 和 \(W_2\) 的平均值称为 \(C_i\)。
最后考虑如何绑定节点的问题,一开始,每个序列只有一个点,他们的 \(C_i\) 就是 \(W_i\),每次操作把 \(C_i\) 最大的点合并到他的父节点并记录贡献,一直合并到根节点即可。
「CSPS 2019 十一」 贪心
原文地址:https://www.cnblogs.com/lyfoi/p/11618253.html