蓝桥杯第十讲--贪心【习题】

前言

蓝桥杯官网:蓝桥杯大赛——全国大学生TMT行业赛事
✨本博客讲解 蓝桥杯C/C++ 备赛所涉及算法知识,此博客为第十讲:贪心【习题】

本篇博客所包含习题有:
👊付账问题
👊乘积最大
👊后缀表达式

贪心【例题】见博客:蓝桥杯第十讲–贪心【例题】

博客内容以题代讲,通过讲解题目的做法来帮助读者快速理解算法内容,需要注意:学习算法不能光过脑,更要实践,请读者务必自己敲写一遍本博客相关代码!!!


付账问题

来源: 第九届蓝桥杯省赛C++A组,第九届蓝桥杯省赛JAVAA组

题目要求

题目描述:

几个人一起出去吃饭是常有的事。

但在结帐的时候,常常会出现一些争执。

现在有 n n n 个人出去吃饭,他们总共消费了 S S S 元。

其中第 i i i 个人带了 a i a_i ai 元。

幸运的是,所有人带的钱的总数是足够付账的,但现在问题来了:每个人分别要出多少钱呢?

为了公平起见,我们希望在总付钱量恰好为 S S S 的前提下,最后每个人付的钱的标准差最小。

这里我们约定,每个人支付的钱数可以是任意非负实数,即可以不是 1 1 1 分钱的整数倍。

你需要输出最小的标准差是多少。

标准差的介绍:标准差是多个数与它们平均数差值的平方平均数,一般用于刻画这些数之间的“偏差有多大”。

形式化地说,设第 i i i 个人付的钱为 b i b_i bi 元,那么标准差为 :

s = 1 n ∑ i = 1 n ( b i − 1 n ∑ i = 1 n b i ) 2 s=\sqrt{\frac{1}{n}\sum_{i=1}^{n}(b_i-\frac{1}{n}\sum_{i=1}^{n}b_i)^2} s=n1i=1n(bin1i=1nbi)2

输入格式:

第一行包含两个整数 n n n S S S

第二行包含 n n n 个非负整数 a 1 ,   … ,   a n a_1, …, a_n a1,,an

输出格式:

输出最小的标准差,四舍五入保留 4 4 4 位小数。

数据范围:

1 ≤ n ≤ 5 × 1 0 5 , 1≤n≤5×10^5, 1n5×105,
0 ≤ a i , S ≤ 1 0 9 0≤a_i,S≤10^9 0ai,S109

输入样例1:

5 2333
666 666 666 666 666

输出样例1:

0.0000

输入样例2:

10 30
2 1 4 7 4 8 3 6 4 7

输出样例2:

0.7928

思路分析

我们对数组从小到大进行排序后遍历数组,贪心的思维是:如果你的钱数够负平均后的钱数,那么就付一个平均的钱数,否则的话,付掉你所有的钱数,然后让你后面的人均摊你不够的钱数。

代码

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>

using namespace std;

const int N = 500010;

int a[N];

int main()
{
    int n;
    double s;
    scanf("%d%lf", &n, &s);
    for (int i = 0; i < n; i ++ ) scanf("%d", &a[i]);
    
    sort(a, a + n);
    
    double res = 0, avg = s / n;
    for (int i = 0; i < n; i ++ ) 
    {
        double cur = s / (n - i);
        if (a[i] < cur) cur = a[i];
        res += (cur - avg) * (cur - avg);
        s -= cur;
    }
    
    printf("%.4lf\n", sqrt(res / n));
    
    return 0;
}

乘积最大

来源: 第九届蓝桥杯省赛C++B组

题目要求

题目描述:

给定 N N N 个整数 A 1 , A 2 , … A N A_1,A_2,…A_N A1,A2,AN

请你从中选出 K K K 个数,使其乘积最大。

请你求出最大的乘积,由于乘积可能超出整型范围,你只需输出乘积除以 1000000009 1000000009 1000000009 的余数。

注意,如果 X < 0 X<0 X<0, 我们定义 X X X 除以 1000000009 1000000009 1000000009 的余数是负 ( − X ) (−X) (X)除以 1000000009 1000000009 1000000009 的余数,即: 0 − ( ( 0 − x ) % 1000000009 ) 0−((0−x)\%1000000009) 0((0x)%1000000009)

输入格式:

第一行包含两个整数 N N N K K K

以下 N N N 行每行一个整数 A i A_i Ai

输出格式:

输出一个整数,表示答案。

数据范围:

1 ≤ K ≤ N ≤ 1 0 5 , 1≤K≤N≤10^5, 1KN105,
− 1 0 5 ≤ A i ≤ 1 0 5 −10^5≤A_i≤10^5 105Ai105

输入样例1:

5 3
-100000
-10000
2
100000
10000

输出样例1:

999100009

输入样例2:

5 3
-100000
-100000
-2
-100000
-100000

输出样例2:

-999999829

思路分析

  • k == n:全选

  • k是偶数个:

    • 负数有偶数个:两个两个选:最小的两个负数或最大的两个正数(乘积最大) res >= 0
    • 负数有奇数个:挑出最大的负数(永远不选),问题转变为负数有偶数个 res >= 0
  • k是奇数个:

    • 全为负数:从最大的负数开始往小选 res < 0
    • 至少有一个非负数:选择这一个非负数,问题转变为k是偶数个 res >= 0

代码

#include <cstdio>
#include <algorithm>
#include <cstring>

using namespace std;

typedef long long LL;

const int N = 100010, MOD = 1000000009;

int n, k;
int a[N];

int main()
{
    scanf("%d%d", &n, &k);
    for (int i = 0; i < n; i ++ ) scanf("%d", &a[i]);
    
    sort(a, a + n);
    
    int res = 1;
    int l = 0, r = n - 1;
    int sign = 1;
    if (k % 2)
    {
        res = a[r -- ];
        k -- ;
        if (res < 0) sign = -1;
    }
    
    while (k)
    {
        LL x = (LL)a[l] * a[l + 1], y = (LL)a[r] * a[r - 1];
        if (x * sign > y * sign)
        {
            res = x % MOD * res % MOD;
            l += 2;
        }
        else
        {
            res = y % MOD * res % MOD;
            r -= 2;
        }
        k -= 2;
    }
    
    printf("%d\n", res);
    
    return 0;
}

后缀表达式

来源: 第十届蓝桥杯省赛C++B组,第十届蓝桥杯省赛JAVAB组

题目要求

题目描述:

给定 N N N 个加号、 M M M 个减号以及 N + M + 1 N+M+1 N+M+1 个整数 A 1 , A 2 , ⋅ ⋅ ⋅ , A N + M + 1 A_1,A_2,⋅⋅⋅,A_{N+M+1} A1,A2,,AN+M+1,小明想知道在所有由这 N N N 个加号、 M M M 个减号以及 N + M + 1 N+M+1 N+M+1 个整数凑出的合法的后缀表达式中,结果最大的是哪一个?

请你输出这个最大的结果。

例如使用 123 + − 123+− 123+,则 “ 23 + 1 − ” “23+1−” 23+1 这个后缀表达式结果是 4 4 4,是最大的。

输入格式:

第一行包含两个整数 N N N M M M

第二行包含 N + M + 1 N+M+1 N+M+1 个整数 A 1 , A 2 , ⋅ ⋅ ⋅ , A N + M + 1 A_1,A_2,⋅⋅⋅,A_{N+M+1} A1,A2,,AN+M+1

输出格式:

输出一个整数,代表答案。

数据范围:

0 ≤ N , M ≤ 1 0 5 , 0≤N,M≤10^5, 0N,M105,
− 1 0 9 ≤ A i ≤ 1 0 9 −10^9≤Ai≤10^9 109Ai109

输入样例:

1 1
1 2 3

输出样例:

4

思路分析

本题最为关键的就是分析出,虽然有 M M M 个减号,但是我们最终呈现出来的其实并非必须是 M M M 个减号,比如我们可以把 M − 1 M-1 M1 个减号放入到括号中,这样就可以改变减号的个数: a 1 − ( a 2 − a 3 − a 4 ) = a 1 − a 2 + a 3 + a 4 a_1-(a_2-a_3-a_4)=a_1-a_2+a_3+a_4 a1(a2a3a4)=a1a2+a3+a4,有 N N N 个加号,但是我们也可以用括号去改变加号的个数: a 1 − ( a 2 + a 3 + a 4 ) = a 1 − a 2 − a 3 − a 4 a_1-(a_2+a_3+a_4)=a_1-a_2-a_3-a_4 a1(a2+a3+a4)=a1a2a3a4,你可能有疑问?本题是后缀表达式,且只有加减两种运算,怎么能出现括号呢?我们知道,每一个后缀表达式都对应一颗树,如: a 1 − a 2 − a 3 − a 4 a_1-a_2-a_3-a_4 a1a2a3a4 对应下图所示树:
在这里插入图片描述
那么我们做简单变化其实就可以实现 a 1 − ( a 2 − a 3 − a 4 ) a_1-(a_2-a_3-a_4) a1(a2a3a4)

在这里插入图片描述
所以,对于 N N N 个加号和 M M M 个减号,我们其实可以构造出 1 1 1 ~ N + M N+M N+M 个减号, 1 1 1 ~ N + M − 1 N+M-1 N+M1个加号,即如果 M > 0 M>0 M>0,则必有最少一个减号,所以我们可以对数组进行一个排序,然后用数组的最大值减去最小值,对于数组的其他元素,我们可以自由它们的加减,故对正数用加法,负数用减法,即每次让 r e s res res 加上各个元素的绝对值即可。

代码

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 200010;

typedef long long LL;

int a[N];

int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n + m + 1; i ++ ) scanf("%d", &a[i]);
    
    sort(a, a + n + m + 1);
    
    LL res = 0;
    if (!m)
        for (int i = 0; i < n + m + 1; i ++ ) res += a[i];
        
    else
    {
        res = a[n + m] - a[0];
        for (int i = 1; i < n + m; i ++ ) res += abs(a[i]);
    }
        
    printf("%lld\n", res);
    
    return 0;
}

  • 15
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
C语言多机调度问题是指将n个作业分配给m台机器,使得完成所有作业的时间最短。这是一个经典的贪心算法问题。在这个问题中,我们需要将n个作业分配给m台机器,使得每台机器的工作时间尽可能平均,从而使得完成所有作业的时间最短。具体实现可以使用两种方法:机器数大于待分配作业数和机器数小于待分配作业数。前者可以直接将每个作业分配给一台机器,后者则需要使用贪心算法,将每个作业分配给当前工作时间最短的机器。 代码实现如下(C语言): ``` #include <stdio.h> #define N 8 //作业数 #define M 5 //机器数 int s[M] = {0,0,0};//每台机器当前已分配的作业总耗时 int main(void) { int time[N] = {17,15,11,9,8,5,4,2};//处理时间按从大到小排序 int maxtime = 0; if(M >= N) { maxtime = setwork1(time,N); } else { maxtime = setwork2(time,N); } printf("最多耗费时间%d。",maxtime); } //机器数大于待分配作业数 int setwork1(int t[],int n) { int i; for(i=0;i<n;i++) { s[i] = t[i]; } int ma = max(s,N); return ma; } //机器数小于待分配作业数 int setwork2(int t[],int n) { int i; int mi = 0; for(i=0;i<n;i++) { mi = min(M); printf("第%d号作业,时间和最小的机器号为%d.时间和为%d:\n",i,mi,s[mi]); s[mi] = s[mi]+t[i]; } int ma = max(s,M); return ma; } //求出目前处理作业的时间和最小的机器号 int min(int m) { int min = 0; int i; for(i=1;i<m;i++) { if(s[min] > s[i]) { min = i; } } return min; } //求最终结果(最长处理时间) int max(int s[],int num) { int max = s[0]; int i; for(i=1;i<num;i++) { if(max < s[i]) { max = s[i]; } } return max; } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

辰chen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值