第六章 贪心二
第六章 贪心二
排序不等式
排队打水
有 n 个人排队到 11 个水龙头处打水,第 i 个人装满水桶所需的时间是 ti,请问如何安排他们的打水顺序才能使所有人的等待时间之和最小?
输入格式
第一行包含整数 n。
第二行包含 n 个整数,其中第 i 个整数表示第 i 个人装满水桶所花费的时间 ti。
输出格式
输出一个整数,表示最小的等待时间之和。
数据范围
1≤n≤10^5
1≤ti≤10^4
输入样例:
7
3 6 1 4 2 5 7
输出样例:
56
分析
假设各个同学的打水时间为:3 6 1 4 2 5 7
并且就按照这个顺序来打水。
当第一个同学打的时候,后面所有同学都要等他,所以等待的总时长要加上一个3 * 6,第二个同学打的时候,后面所有同学也都要等他,所以要加上个6 * 5,以此类推,所有同学等待的总时长为3 * 6 + 6 * 5 + 1 * 4 + 4 * 3 + 2 * 2 + 5 * 1
假设各个同学打水花费的时长为 t1,t2,t3,…,tn,则按照次序打水,总的等待时长为:t1 * (n-1) + t2 * (n-2) + ... + tn * 1。
证明
采用反证法(调整法),假设最优解不是按照从小到大的顺序,则必然存在2个相邻的人,前一个人打水时长比后一个大,即必然存在一个位置i,满足t_i > t_i+1,那我们尝试把这两个同学的位置交换,看看对总的等待时长有什么影响,这两个同学的交换,只会影响他们两的等待时长,不会影响其他同学的等待时长。
交换前,这部分等待时长为t_i * (n-i) + t_i+1 * (n-i-1),交换后,这部分等待时长为t_i+1 * (n-i) + t_i * (n-i-1),容易算得,交换后的等待时长变小了,则该方案不是最优解,矛盾了。则最优解就是按照从小到大的顺序依次打水。
Code
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N = 100010;
int t[N];
int main() {
int n;
cin >> n;
for (int i = 0; i < n; i++) cin >> t[i];
sort(t, t + n );
LL res = 0;
for (int i = 0; i < n; i++) res += (n - i - 1) * t[i];
cout << res << endl;
}
绝对值不等式
货仓选址
在一条数轴上有 N 家商店,它们的坐标分别为 A1∼AN。
现在需要在数轴上建立一家货仓,每天清晨,从货仓到每家商店都要运送一车商品。
为了提高效率,求把货仓建在何处,可以使得货仓到每家商店的距离之和最小。
输入格式
第一行输入整数 N。
第二行 N 个整数 A1∼AN。
输出格式
输出一个整数,表示距离之和的最小值。
数据范围
1≤N≤100000,
0≤Ai≤40000
输入样例:
4
6 2 9 1
输出样例:
12
分析
假设n个商店在数轴上的坐标依次为:x1,x2,x3,…,xn
设仓库的位置为x,则总的距离为
f(x) = |x1 - x| + |x2 - x| + ... + |xn - x|
我们要求解的就是f(x)的最小值。
我们可以先进行一下分组,1和n为一组,2和n-1为一组…
f(x) = (|x1 - x| + |xn - x|) + (|x2 - x| + |x_n-1 - x|) + ....
单独看一组,任意一组都可以写成形如|a - x| + |b - x|的形式,a和b是已知的常数,x是未知数。假设a < b,则容易知道,当x取值在[a,b]这个区间内时,上面的表达式取得最小值b - a,而x取值只要落在[a,b]区间外,则上面的表达式的值一定是大于b - a的。
由此可知,对于分组1和n,只要x取值在[x1,xn]这个区间内,就能使|x1 - x| + |xn - x|取得最小值xn - x1。同理,对于|x2 - x| + |x_n-1 - x|,只要x取值在[x2,x_n-1]区间内,就能使这个部分取得最小值x_n-1 - x2…容易得出,只要取所有分组的区间的交集,即能使总的f(x)最小。即,当n为偶数时,x只要落在最中间2个点之间即可;当n为奇数时,x只需要落在最中间的那个点上即可。
Code
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100010;
int a[N];
int main() {
int n;
cin >> n;
for (int i = 0; i < n; i++) cin >> a[i];
sort(a, a + n);
int res = 0;
for (int i = 0; i < n; i++) res += abs(a[i] - a[n / 2]);
cout << res << endl;
return 0;
}
推公式
耍杂技的牛
农民约翰的 N 头奶牛(编号为 1..N)计划逃跑并加入马戏团,为此它们决定练习表演杂技。
奶牛们不是非常有创意,只提出了一个杂技表演:
叠罗汉,表演时,奶牛们站在彼此的身上,形成一个高高的垂直堆叠。
奶牛们正在试图找到自己在这个堆叠中应该所处的位置顺序。
这 N 头奶牛中的每一头都有着自己的重量 Wi 以及自己的强壮程度 Si。
一头牛支撑不住的可能性取决于它头上所有牛的总重量(不包括它自己)减去它的身体强壮程度的值,现在称该数值为风险值,风险值越大,这只牛撑不住的可能性越高。
您的任务是确定奶牛的排序,使得所有奶牛的风险值中的最大值尽可能的小。
输入格式
第一行输入整数 N,表示奶牛数量。
接下来 N 行,每行输入两个整数,表示牛的重量和强壮程度,第 i 行表示第 i 头牛的重量 Wi 以及它的强壮程度 Si。
输出格式
输出一个整数,表示最大风险值的最小可能值。
数据范围
1≤N≤50000
1≤Wi≤10,000
1≤Si≤1,000,000,000
输入样例:
3
10 3
2 5
3 3
输出样例:
2
分析
结论:按照w + s从小到大的顺序,从上往下排,最大的危险系数一定是最小的。
简单理解:把重量轻的牛放下面是很亏的,同样把不强壮的牛放下面也是亏的,所以就尽可能把又重又强壮的牛放下面。
证明
从两方面证明:
- 按照上面策略得到的答案
>=最优解 - 按照上面策略得到的答案
<=最优解
首先,按照上面的策略得到的是一种方案,而最优解是所有方案中的最小值,所以 按照上面策略得到的答案 >= 最优解。
第二点,用反证法。假设最优解不是按照w + s从小到大排列。则一定存在一个位置i,使得wi + si > w(i+1) + s(i+1),然后看一下把这两头牛交换,会发生什么变化。同样的,这两头牛的交换,不会影响除这两头牛以外,其他牛的危险系数,所以只看这两头牛的危险系数的变化即可。交换前和交换后,第i和第i+1头牛的危险系数如下
| 第i个位置上的牛 | 第i+1个位置上的牛 | |
|---|---|---|
| 交换前 | w1 + w2 + … + w(i-1) - si | w1 + w2 + … + wi - s(i+1) |
| 交换后 | w1 + w2 + … + w(i-1) - s(i+1) | w1 + w2 + … + w(i-1) + w(i+1) - si |
去掉公共部分 w1 + w2 + … + w(i-1)
| 第i个位置上的牛 | 第i+1个位置上的牛 | |
|---|---|---|
| 交换前 | - si | wi - s(i+1) |
| 交换后 | - s(i+1) | w(i+1) - si |
随后,对所有项加上一个 si + s(i+1),转化为正数,方便我们比较
| 第i个位置上的牛 | 第i+1个位置上的牛 | |
|---|---|---|
| 交换前 | s(i+1) | wi + si |
| 交换后 | s(i) | w(i+1) + s(i+1) |
由于wi + si > si,且根据先前的假设,有wi + si > w(i+1) + s(i+1)
所以wi + si 大于 max(si, w(i+1) + s(i+1) ) ,进而有 max( s(i+1), wi + si) > max(si, w(i+1) + s(i+1) )。即,交换后,第i个和第i+1个位置上的牛中的最大危险系数变小了。
所以,只要存在一个位置i,使得wi + si > w(i+1) + s(i+1),则一定能交换第i和第i+1的位置,使得总体的最大的危险系数不变或者变小。假设最优解不是按照w + s从小到大的顺序排列,则我们通过贪心策略,总是能够交换2个逆序的位置,使得到的结果,不变或者变得更小。因此,贪心得到的答案一定是 <= 最优解的。
Code
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 50010;
struct Cow {
int w, s;
bool operator< (const Cow &c) {
return w + s < c.w + c.s;
}
} cow[N];
int main() {
int n;
cin >> n;
for (int i = 0; i < n; i++) {
int w, s;
cin >> w >> s;
cow[i] = {w,s};
}
sort(cow, cow + n);
int res = -2e9, wSum = 0;
for (int i = 0; i < n; i++) {
res = max(res, wSum - cow[i].s);
wSum += cow[i].w;
}
cout << res << endl;
return 0;
}

浙公网安备 33010602011771号