【基础算法】贪心 (二) :推公式
文章目录
- 什么是推公式
- 1. 拼数 ⭐⭐
- (1) 解题思路
- (2) 代码实现
- 2. Protecting the Flowers S ⭐⭐⭐
- (1) 解题思路
- (2) 代码实现
- 3. 奶牛玩杂技 ⭐⭐⭐
- (1) 解题思路
- (2) 代码实现
什么是推公式
如果细说的话,本篇标题应该叫推公式 + 排序。推公式就是寻找排序规则,排序就是在该排序规则下对整个对象排序。 在解决某些问题的时,当我们发现最终结果需要调整每个对象的先后顺序,也就是对整个对象排序时,那么我们就可以用推公式的方式,得出我们的排序规则,进而对整个对象排序。
正确性证明:
利用排序解决问题,最重要的就是需要证明"在新的排序规则下,整个集合可以排序"。这需要用到离散数学中"全序关系"的知识。但是证明过程很麻烦,后续题目中我们只要发现该题最终结果需要排序,并且交换相邻两个元素的时候,对其余元素不会产生影响,那么我们就可以推导出排序的规则,然后直接去排序,就不去证明了。
1. 拼数 ⭐⭐
【题目链接】
[P1012 NOIP 1998 提高组] 拼数 - 洛谷
【题目描述】
设有 n n n 个正整数 a 1 … a n a_1 \dots a_n a1…an,将它们联接成一排,相邻数字首尾相接,组成一个最大的整数。
【输入格式】
第一行有一个整数,表示数字个数 n n n。
第二行有 n n n 个整数,表示给出的 n n n 个整数 a i a_i ai。
【输出格式】
一个正整数,表示最大的整数
【示例一】
输入
3 13 312 343
输出
34331213
【示例二】
输入
4 7 13 4 246
输出
7424613
【说明/提示】
对于全部的测试点,保证 1 ≤ n ≤ 20 1 \leq n \leq 20 1≤n≤20, 1 ≤ a i ≤ 1 0 9 1 \leq a_i \leq 10^9 1≤ai≤109。
NOIP1998 提高组 第二题
(1) 解题思路
我们发现,任取序列里面相邻的两项 a[i]
和 a[i + 1]
,交换他们的顺序,并不影响 a[0] ~ a[i - 1]
与 a[i + 2] ~ a[n]
之间每一位的权值。因此我们可以找一种比较方式,对整个数组排序,最终的结果就是最优序列。
设两个相邻的数对应的字符串形式为 x, y ,因为要的是最大值,所以自定义比较方式:
-
x + y > y + x
:x 放在前面,y 放在后面; -
x + y < y + x
:y 放在前面,x 放在后面; -
x + y = y + x
:谁前谁后无所谓。
(2) 代码实现
#include<iostream>
#include<algorithm>using namespace std;const int N = 25;
string s[N];bool cmp(string& a, string& b)
{return a + b > b + a;
}int main()
{int n; cin >> n;for(int i = 1; i <= n; i++) cin >> s[i];sort(s + 1, s + 1 + n, cmp);string ans = "";for(int i = 1; i <= n; i++) ans += s[i];cout << ans;return 0;
}
2. Protecting the Flowers S ⭐⭐⭐
【题目链接】
[P2878 USACO07JAN] Protecting the Flowers S - 洛谷
【题目描述】
有 n n n 头奶牛跑到 FJ 的花园里去吃花儿了,它们分别在距离牛圈 T i T_i Ti(这里指 FJ 到那里需要 T i T_i Ti 分钟)处吃花,每分钟会吃掉 D i D_i Di 朵花,FJ 现在要将它们给弄回牛圈,但是他每次只能弄一头回去,来回用时总共为 2 × T i 2 \times T_i 2×Ti 分钟,在这段时间内,其它的奶牛会继续吃 FJ 的花,速度保持不变,当然正在被赶回牛圈的奶牛不能继续吃了。现在求在最好的方案下奶牛吃掉花的最小朵数。
【输入格式】
第一行一个正整数 n n n。
下面 n n n 行,每行两个正整数 T i , D i T_i,D_i Ti,Di。
【输出格式】
一行一个整数表示答案。
【示例一】
输入
6 3 1 2 5 2 3 3 2 4 1 1 6
输出
86
【说明/提示】
样例解释:最优策略是按 6 → 2 → 3 → 4 → 1 → 5 6 \to 2 \to 3 \to 4 \to 1 \to 5 6→2→3→4→1→5 的顺序把牛赶回牛圈。
对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 1 0 5 1 \le n \le 10^5 1≤n≤105, 1 ≤ L i ≤ 2 × 1 0 6 1 \le L_i \le 2 \times 10^6 1≤Li≤2×106, 1 ≤ D i ≤ 100 1 \le D_i \le 100 1≤Di≤100。
(1) 解题思路
我们会发现,在一个“赶牛”序列 c 中,任意交换两头牛 c[i]
和 c[i + 1]
的顺序之后,区间 [1, i - 1]
以及 [i + 2, n]
内的所有牛吃草的总量是不变的。因此我们可以找一种比较方式,通过调整这相邻两头牛的顺序而达到更优的效果,那么对整个序列排序,最终结果就是最优序列。
如何寻找这个比较方式?
假设我们要交换的两头牛的下标为 i i i 和 j j j ( i + 1 = j i + 1=j i+1=j),到达 i i i 位置时,经过的时间为 T T T。
- 不交换 i i i、 j j j 的顺序
第 i i i 头牛的吃草量: T × d i T\times d_i T×di
第 j j j 头牛的吃草量: ( T + 2 × t i ) × d j (T+ 2 \times t_i)\times d_j (T+2×ti)×dj
总共的吃草量: s u m 1 = \operatorname{sum_1}= sum1= T d i + T d j + 2 t i d j Td_i+Td_j+2t_id_j Tdi+Tdj+2tidj。
- 交换 i i i、 j j j 的顺序
第 i i i 头牛的吃草量: T × d j T\times d_j T×dj
第 j j j 头牛的吃草量: ( T + 2 × t j ) × d i (T+ 2 \times t_j)\times d_i (T+2×tj)×di
总共的吃草量: s u m 2 = \operatorname{sum_2}= sum2= T d j + T d i + 2 t j d i Td_j+Td_i+2t_jd_i Tdj+Tdi+2tjdi。
如果 i i i 在 j j j 之前比较好,那么应该满足 s u m 1 < s u m 2 \operatorname{sum_1} < \operatorname{sum_2} sum1<sum2,我们便可以得到:
t i d j < t j d i t_id_j < t_jd_i tidj<tjdi
综上,我们可以得到排序规则如下:
- 当 t i d j < t j d i t_id_j < t_jd_i tidj<tjdi 时: i i i 排在 j j j 之前;
- 当 t i d j > t j d i t_id_j > t_jd_i tidj>tjdi 时: i i i 排在 j j j 之后;
- 当 t i d j = t j d i t_id_j = t_jd_i tidj=tjdi 时:谁前谁后无所谓。
(2) 代码实现
#include<iostream>
#include<algorithm>using namespace std;typedef long long LL;const int N = 1e5 + 10;struct cow
{int t;int d;
} c[N];bool cmp(cow& c1, cow& c2) // 排序规则
{return c1.t * c2.d < c2.t * c1.d;
}int main()
{int n; cin >> n;for(int i = 1; i <= n; i++) cin >> c[i].t >> c[i].d;sort(c + 1, c + n + 1, cmp);LL ans = 0, time = 0;for(int i = 1; i <= n; i++){ans += c[i].d * time;time += 2 * c[i].t;}cout << ans;return 0;
}
3. 奶牛玩杂技 ⭐⭐⭐
【题目链接】
[P1842 USACO05NOV] 奶牛玩杂技 - 洛谷
【题目背景】
Farmer John 养了 N N N 头牛,她们已经按 1 ∼ N 1\sim N 1∼N 依次编上了号。FJ 所不知道的是,他的所有牛都梦想着从农场逃走,去参加马戏团的演出。可奶牛们很快发现她们那笨拙的蹄子根本无法在钢丝或晃动的的秋千上站稳(她们还尝试过把自己装在大炮里发射出去,但可想而知,结果是悲惨的) 。最终,她们决定练习一种最简单的杂技:把所有牛都摞在一起, 比如说, 第一头牛站在第二头的身上, 同时第二头牛又站在第三头牛的身上…最底下的是第 N N N 头牛。
【题目描述】
每头牛都有自己的体重以及力量,编号为 i i i 的奶牛的体重为 W i W_i Wi,力量为 S i S_i Si。
当某头牛身上站着另一些牛时它就会在一定程度上被压扁,我们不妨把它被压扁的程度叫做它的压扁指数。对于任意的牛,她的压扁指数等于摞在她上面的所有奶牛的总重(当然不包括她自己)减去它的力量。奶牛们按照一定的顺序摞在一起后, 她们的总压扁指数就是被压得最扁的那头奶牛的压扁指数。
你的任务就是帮助奶牛们找出一个摞在一起的顺序,使得总压扁指数最小。
【输入格式】
第一行一个整数 N N N。
接下来 N N N 行,每行两个整数 W i W_i Wi 和 S i S_i Si。
【输出格式】
一行一个整数表示最小总压扁指数。
【示例一】
输入
3 10 3 2 5 3 3
输出
2
【说明/提示】
对于 100 % 100\% 100% 的数据, 1 ≤ N ≤ 5 × 1 0 4 1 \le N \le 5\times 10^4 1≤N≤5×104, 1 ≤ W i ≤ 1 0 4 1 \le W_i \le 10^4 1≤Wi≤104, 1 ≤ S i ≤ 1 0 9 1 \le S_i \le 10^9 1≤Si≤109。
(1) 解题思路
与上一道题类似,我们会发现,在一个“摞牛”序列 c 中,任意交换两头牛 c[i]
和 c[i + 1]
的顺序之后,区间 [1, i - 1]
以及 [i + 2, n]
内的所有牛压扁指数是不变的。因此我们可以找一种比较方式,通过调整这相邻两头牛的顺序而达到更优的效果,那么对整个序列排序,最终结果就是最优序列。
假设我们要交换的两头牛的下标为 i i i 和 j j j ( i + 1 = j i + 1=j i+1=j),第 $1 $ 至 i − 1 i - 1 i−1 头牛的总重量为 W W W。
- 不交换 i i i、 j j j 的顺序
第 i i i 头牛的压扁指数: W − s i W-s_i W−si
第 j j j 头牛的压扁指数: W + w i − s j W + w_i-s_j W+wi−sj
这两头牛的总压扁指数: i n d e x 1 = max ( W − s i , W + w i − s j ) \operatorname{index_1}=\operatorname{max}(W-s_i\ ,\ W + w_i-s_j) index1=max(W−si , W+wi−sj)。
- 交换 i i i、 j j j 的顺序
第 i i i 头牛的吃草量: W − s j W-s_j W−sj
第 j j j 头牛的吃草量: W + w j − s i W + w_j - s_i W+wj−si
这两头牛的总压扁指数: i n d e x 2 = max ( W − s j , W + w j − s i ) \operatorname{index_2}=\operatorname{max}(W-s_j\ , \ W + w_j - s_i) index2=max(W−sj , W+wj−si)。
如果 i i i 在 j j j 之前比较好,那么应该满足 i n d e x 1 < i n d e x 2 \operatorname{index_1} < \operatorname{index_2} index1<index2,我们便可以得到:
max ( W − s i , W + w i − s j ) < max ( W − s j , W + w j − s i ) \operatorname{max}(W-s_i\ ,\ W + w_i-s_j) < \operatorname{max}(W-s_j\ , \ W + w_j - s_i) max(W−si , W+wi−sj)<max(W−sj , W+wj−si)
每一项都含有 W W W,则可以化简得到:
max ( − s i , w i − s j ) < max ( − s j , w j − s i ) \operatorname{max}(-s_i\ ,\ w_i-s_j) < \operatorname{max}(-s_j\ , \ w_j - s_i) max(−si , wi−sj)<max(−sj , wj−si)
上面的式子还可以继续化简为下面的式子:
s i + w i < s j + w j s_i + w_i < s_j + w_j si+wi<sj+wj
化简过程从略,分类讨论即可。
(2) 代码实现
#include<iostream>
#include<algorithm>using namespace std;typedef long long LL;const int N = 5e4 + 10;struct cow
{int w;LL s;
} c[N];bool cmp(cow& c1, cow& c2)
{// return max(-c1.s, c1.w - c2.s) < max(-c2.s, c2.w - c1.s);return c1.s + c1.w < c2.s + c2.w;
}int main()
{int n; cin >> n;for(int i = 1; i <= n; i++) cin >> c[i].w >> c[i].s;sort(c + 1, c + n + 1, cmp);// 总压扁指数,i位置以上的体重总和,i位置以上压扁指数LL ans = -1e9 - 10, sum = 0, index;for(int i = 1; i <= n; i++){index = sum - c[i].s;ans = max(index, ans);sum += c[i].w;}cout << ans;return 0;
}