加载中...

第六章 贪心二

第六章 贪心二

排序不等式

排队打水

有 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个商店在数轴上的坐标依次为:x1x2x3,…,xn

设仓库的位置为x,则总的距离为

f(x) = |x1 - x| + |x2 - x| + ... + |xn - x|

我们要求解的就是f(x)的最小值。

我们可以先进行一下分组,1n为一组,2n-1为一组…

f(x) = (|x1 - x| + |xn - x|) + (|x2 - x| + |x_n-1 - x|) + ....

单独看一组,任意一组都可以写成形如|a - x| + |b - x|的形式,ab是已知的常数,x是未知数。假设a < b,则容易知道,当x取值在[a,b]这个区间内时,上面的表达式取得最小值b - a,而x取值只要落在[a,b]区间外,则上面的表达式的值一定是大于b - a的。

由此可知,对于分组1n,只要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;
}
posted @ 2023-03-15 14:53  Chenjq12  阅读(47)  评论(0)    收藏  举报