蓝桥杯C++ AB组辅导课 第五讲 贪心 Acwing

例题

AcWing 1055. 股票买卖 II

给定一个长度为 N 的数组,数组中的第 i 个数字表示一个给定股票在第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

输入格式
第一行包含整数 N,表示数组长度。

第二行包含 N 个不大于 10000 的正整数,表示完整的数组。

输出格式
输出一个整数,表示最大利润。

数据范围
1≤N≤105
输入样例1:
6
7 1 5 3 6 4
输出样例1:
7
输入样例2:
5
1 2 3 4 5
输出样例2:
4
输入样例3:
5
7 6 4 3 1
输出样例3:
0
样例解释
样例1:在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。共得利润 4+3 = 7。

样例2:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。

样例3:在这种情况下, 不进行任何交易, 所以最大利润为 0。

题解1 :

  • 状态机模型
  • f[i][0]表示考虑前i天,第i天持股状态为0的方案的收益的最大值
#include <iostream>
using namespace std;

const int N = 1e5 + 10;

int n, w[N];
int f[N][2];

int main()
{
    cin >> n;
    for (int i = 1; i <= n && cin >> w[i]; i ++ );
    
    f[0][1] = -1e9;
    for (int i = 1; i <= n; i ++ )
    {
        f[i][0] = max(f[i - 1][0], f[i - 1][1] + w[i]);
        f[i][1] = max(f[i - 1][1], f[i - 1][0] - w[i]);
    }
    
    cout << max(f[n][0], f[n][1]);
}

// 贪心
#include <iostream>
using namespace std;

const int N = 1e5 + 10;

int n, w[N];

int main()
{
    cin >> n;
    for (int i = 1; i <= n && cin >> w[i]; i ++ );
    
    int res = 0;
    for (int i = 2; i <= n; i ++ )
    {
        if (w[i] > w[i - 1])
            res += w[i] - w[i - 1];
    }
    cout << res;
}

AcWing 104. 货仓选址

在一条数轴上有 N 家商店,它们的坐标分别为 A1∼AN。

现在需要在数轴上建立一家货仓,每天清晨,从货仓到每家商店都要运送一车商品。

为了提高效率,求把货仓建在何处,可以使得货仓到每家商店的距离之和最小。

输入格式
第一行输入整数 N。

第二行 N 个整数 A1∼AN。

输出格式
输出一个整数,表示距离之和的最小值。

数据范围
1≤N≤100000,
0≤Ai≤40000
输入样例:
4
6 2 9 1
输出样例:
12

#include <iostream>
#include <algorithm>
using namespace std;

typedef long long ll;

const int N = 1e5 + 10;

int n, a[N];

int main()
{
    cin >> n;
    for (int i = 1; i <= n && cin >> a[i]; i ++ );
    sort(a + 1, a + n + 1);
    
    ll res = 0;
    for (int i = 1; i <= n; i ++ )
        res += abs(a[n / 2 + 1] - a[i]);
    cout << res;
}

AcWing 122. 糖果传递

有 n 个小朋友坐成一圈,每人有 a[i] 个糖果。

每人只能给左右两人传递糖果。

每人每次传递一个糖果代价为 1。

求使所有人获得均等糖果的最小代价。

输入格式
第一行输入一个正整数 n,表示小朋友的个数。

接下来 n 行,每行一个整数 a[i],表示第 i 个小朋友初始得到的糖果的颗数。

输出格式
输出一个整数,表示最小代价。

数据范围
1≤n≤1000000,
0≤a[i]≤2×109,
数据保证一定有解。

输入样例:
4
1
2
5
4
输出样例:
4

题解 :

  • a v g = s u m / n avg = sum / n avg=sum/n为每个小朋友最后的糖果数
  • x i x_i xi为第i个小朋友给第i-1个小朋友的糖果数,如果是i-1给i,那么这个值为负数;其中, x 1 x_1 x1表示第1个小朋友给第n个小朋友
  • 则, a n s = ∣ x 1 ∣ + . . + ∣ x n ∣ ans=|x_1|+..+|x_n| ans=x1+..+xn
  • 且, a 1 − x 1 + x 2 = a v g a_1-x_1+x_2=avg a1x1+x2=avg a 2 − x 2 + x 3 = a v g a_2-x_2+x_3=avg a2x2+x3=avg,…
  • 观察上述式子,发现从所有x_i都可以由x_1表示而成,其中,a_i和avg皆已知,那么,ans就变成了只有x_1一个变量的式子
    x 2 = x 1 − a 1 + a v g x_2=x_1-a_1+avg x2=x1a1+avg
    x 3 = x 1 − a 1 − a 2 + 2 a v g x_3=x_1-a_1-a_2+2avg x3=x1a1a2+2avg
    x 4 = x 1 − a 1 − a 2 − a 3 + 3 a v g x_4=x_1-a_1-a_2-a_3+3avg x4=x1a1a2a3+3avg
  • 发现形式很固定,且它们都在绝对值中,发现和 货仓选址 的式子很像,想办法化成 ∣ x 1 − c i ∣ |x_1-c_i| x1ci相加的形式
  • c i = a 1 + a 2 + . . . + c i − 1 − ( n − 1 ) a v g c_i=a_1+a_2+...+c_{i-1}-(n-1)avg ci=a1+a2+...+ci1(n1)avg,又发现,这是一个前缀和的式子,即, c i = c i − 1 − a v g c_i=c_{i-1}-avg ci=ci1avg
#include <iostream>
#include <algorithm>
using namespace std;

typedef long long ll;

const int N = 1e6 + 10;

int n, a[N], c[N];

int main()
{
    cin >> n;
    ll sum = 0, ans = 0;
    for (int i = 1; i <= n && cin >> a[i]; i ++ ) sum += a[i];
    
    ll avg = sum / n;
    
    for (int i = 1; i <= n; i ++ ) c[i] = c[i - 1] + a[i] - avg;
    sort(c + 1, c + n + 1);
    
    for (int i = 1; i <= n; i ++ )
        ans += abs(c[n / 2 + 1] - c[i]);
    cout << ans;
}

AcWing 112. 雷达设备

假设海岸是一条无限长的直线,陆地位于海岸的一侧,海洋位于另外一侧。

每个小岛都位于海洋一侧的某个点上。

雷达装置均位于海岸线上,且雷达的监测范围为 d,当小岛与某雷达的距离不超过 d 时,该小岛可以被雷达覆盖。

我们使用笛卡尔坐标系,定义海岸线为 x 轴,海的一侧在 x 轴上方,陆地一侧在 x 轴下方。

现在给出每个小岛的具体坐标以及雷达的检测范围,请你求出能够使所有小岛都被雷达覆盖所需的最小雷达数目。

输入格式
第一行输入两个整数 n 和 d,分别代表小岛数目和雷达检测范围。

接下来 n 行,每行输入两个整数,分别代表小岛的 x,y 轴坐标。

同一行数据之间用空格隔开。

输出格式
输出一个整数,代表所需的最小雷达数目,若没有解决方案则所需数目输出 −1。

数据范围
1≤n≤1000,
−1000≤x,y≤1000
输入样例:
3 2
1 2
-3 1
2 1
输出样例:
2

题解 :

  • 由每个海岛都可以在海岸线上得到一个区间,区间左端点 x − d 2 − y 2 x-\sqrt{d^2-y^2} xd2y2 ,区间右端点 x + d 2 + y 2 x+\sqrt{d^2+y^2} x+d2+y2
  • 问题转换成在数轴上选择尽可能少的点,保证所有区间内至少有一个点
  • 贪心 :将所有区间按右端点从小到大排序;依次枚举每个区间,如果当前区间内已经包含最后一个选择的点,就跳过;否则,当前区间的右端点被选择,作为最后一个选择的点
#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;

const int N = 1e3 + 10;

struct Segment
{
    double l, r;
    
    bool operator< (const Segment &w) const
    {
        return r < w.r;
    }
}seg[N];

int main()
{
    int n, d; cin >> n >> d;
    
    bool failed = false;
    for (int i = 0, x, y; i < n && cin >> x >> y; i ++ )
    {
        if (y > d)
        {
            failed = true;
            break;
        }
        double tmp = sqrt(d * d - y * y);
        seg[i].l = x - tmp, seg[i].r = x + tmp;
    }
    
    if (failed)
    {
        cout << -1;
    }
    else
    {
        sort(seg, seg + n);
        
        double last = -1e20;
        int cnt = 0;
        for (int i = 0; i < n; i ++ )
            if (last < seg[i].l)
                cnt ++ , last = seg[i].r;
        cout << cnt;
    }
}

习题

AcWing 1235. 付账问题

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

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

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

其中第 i 个人带了 ai 元。

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

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

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

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

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

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

p1.png

输入格式
第一行包含两个整数 n、S;

第二行包含 n 个非负整数 a1,…,an。

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

数据范围
1≤n≤5×105,
0≤ai≤109,
0≤S≤1015。

输入样例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

题解 :

  • 这题一看就是贪心(废话), 那么怎么个贪心法呢?首先我们要知道标准差表示的是数据的波动程度,其值越大波动越大。要使得标准差小,我们就要尽可能使得数据都比较接近平均值。那么这题贪心策略应该是这样的:首先算出平均值s/ns/n,把数据从小到大排序,如果某个人的钱低于该值,那么他一定是将钱全部支付,然后其余不够的其他人平摊。但是,由于之前那个人钱不够,那么就会导致剩下人支付的平均值会增大,所以在这个平摊过程中很有可能存在某个人钱又低于这个平均值,又需要剩下的人平摊。如此反复,直到支付完成。
  • long double “.4Lf”
#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;

const int N = 5e5 + 10;

int n;
long double s;
int a[N];

int main()
{
    cin >> n >> s;
    for (int i = 0; i < n && cin >> a[i]; i ++ );
    sort(a, a + n);		// 必须从小到大考虑,先从可能达不到平均值的开始
    
    long double avg = s / n, sum = 0;
    for (int i = 0; i < n; i ++ )
    {
//        long double cur = s / (n - i);
//        if (a[i] < cur) cur = a[i];
        long double cur = min(s / (n - i), (long double)a[i]);
        sum += (cur - avg) * (cur - avg);
        s -= cur;
    }
    printf("%.4Lf\n", sqrt(sum / n));
}

AcWing 1239. 乘积最大

给定 N 个整数 A1,A2,…AN。

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

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

注意,如果 X<0, 我们定义 X 除以 1000000009 的余数是负(−X)除以 1000000009 的余数,即:0−((0−x)%1000000009)
输入格式
第一行包含两个整数 N 和 K。

以下 N 行每行一个整数 Ai。

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

数据范围
1≤K≤N≤105,
−105≤Ai≤105
输入样例1:
5 3
-100000
-10000
2
100000
10000
输出样例1:
999100009
输入样例2:
5 3
-100000
-100000
-2
-100000
-100000
输出样例2:
-999999829

题解 :

  • 如果k为n,则全选;否则 :
  • 如果k为偶数,选择偶数个正数和偶数个负数,且必然结果为正。因此,每次选择两个最大的或者两个最小的
  • 如果k为奇数,就是 k为偶数的情况下选一个最大的数mx。如果mx为正显然可行,如果mx为负,说明所有数都负,在分为k偶数的情况下得到的是一个正数,为了让最终结果最大,这里选一个绝对值最小的负数
#include <iostream>
#include <algorithm>
using namespace std;

typedef long long ll;

const int N = 1e5 + 10, mod = 1e9 + 9;

int n, k, a[N];

int main()
{
    cin >> n >> k;
    for (int i = 0; i < n && cin >> a[i]; i ++ );
    
    sort(a, a + n);
    
    int res = 1;
    int l = 0, r = n - 1;
    
    if (k % 2)
    {
        res = a[r -- ];
        k -- ;
    }
    
    while (k)
    {
        ll x = (ll)a[l] * a[l + 1], y = (ll)a[r] * a[r - 1];
        if (x * res > y * res)
        {
            res = x % mod * res % mod;
            l += 2;
        }
        else
        {
            res = y % mod * res % mod;
            r -= 2;
        }
        k -= 2;
    }
    
    cout << res;
}
#include <iostream>
#include <algorithm>
using namespace std;

typedef long long ll;

const int N = 1e5 + 10, mod = 1e9 + 9;

int n, k, a[N];

int main()
{
    cin >> n >> k;
    for (int i = 0; i < n && cin >> a[i]; i ++ );
    
    sort(a, a + n);
    
    int res = 1;
    int l = 0, r = n - 1;
    
    if (k % 2)
    {
        res = a[r -- ];
        k -- ;
    }
    
    while (k)
    {
        ll x = (ll)a[l] * a[l + 1], y = (ll)a[r] * a[r - 1];
        if (x * res > y * res)
        {
            res = x % mod * res % mod;
            l += 2;
        }
        else
        {
            res = y % mod * res % mod;
            r -= 2;
        }
        k -= 2;
    }
    
    cout << res;
}

AcWing 1247. 后缀表达式

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

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

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

输入格式
第一行包含两个整数 N 和 M。

第二行包含 N+M+1 个整数 A1,A2,···,AN+M+1。

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

数据范围
0≤N,M≤105,
−109≤Ai≤109
输入样例:
1 1
1 2 3
输出样例:
4

题解 :

  • 这道题不是简单的利用加号和减号即可。注意到是构造后缀表达式,相当于是中缀表达式中可以任意使用若干个括号;当然,只需要最多一对括号
  • 对于正数我们都想加,对于负数我们都想减,但现在的问题是加号减号数量都有限制,解决方案就是用一个负号,可以将加号变成减号,减号变成加号
  • 设最大数为mx,最小数为mi;发现,只要先构造出mx-(mi)的形式,剩下的数中,如果是正数,我们要加,只要在括号外用一个加号或者括号内用一个负号;如果是负数,我们要减,只要在括号外用一个减号或者括号内用一个加号,也就是说,无论正数还是负数,用加号或者负号都能达到最大化结果的效果,因此,直接加上剩下的值的绝对值即可
  • 特殊情况,没有负号,直接把所有数相加即可
#include <iostream>
#include <algorithm>
using namespace std;

typedef long long ll;

const int N = 2e5 + 10;

int n, m, a[N];

int main()
{
    cin >> n >> m;
    
    int k = n + m + 1;
    for (int i = 1; i <= k && cin >> a[i]; i ++ );
    sort(a + 1, a + k + 1);
    
    ll res = 0;
    
    if (!m)
    {
        for (int i = 1; i <= k; i ++ ) res += a[i];
        cout << res;
        return 0;
    }
    
    res += a[k]; res -= a[1]; m -- ;
    for (int i = 2; i < k; i ++ ) res += abs(a[i]);
    cout << res;
}

AcWing 1248. 灵能传输

在游戏《星际争霸 II》中,高阶圣堂武士作为星灵的重要 AOE 单位,在游戏的中后期发挥着重要的作用,其技能”灵能风暴“可以消耗大量的灵能对一片区域内的敌军造成毁灭性的伤害。

经常用于对抗人类的生化部队和虫族的刺蛇飞龙等低血量单位。

你控制着 n 名高阶圣堂武士,方便起见标为 1,2,···,n。

每名高阶圣堂武士需要一定的灵能来战斗,每个人有一个灵能值 ai 表示其拥有的灵能的多少(ai 非负表示这名高阶圣堂武士比在最佳状态下多余了 ai 点灵能,ai 为负则表示这名高阶圣堂武士还需要 −ai 点灵能才能到达最佳战斗状态)。

现在系统赋予了你的高阶圣堂武士一个能力,传递灵能,每次你可以选择一个 i∈[2,n−1],若 ai≥0 则其两旁的高阶圣堂武士,也就是 i−1、i+1 这两名高阶圣堂武士会从 i 这名高阶圣堂武士这里各抽取 ai 点灵能;若 ai<0 则其两旁的高阶圣堂武士,也就是 i−1,i+1 这两名高阶圣堂武士会给 i 这名高阶圣堂武士 −ai 点灵能。

形式化来讲就是 ai−1+=ai,ai+1+=ai,ai−=2ai。

灵能是非常高效的作战工具,同时也非常危险且不稳定,一位高阶圣堂武士拥有的灵能过多或者过少都不好,定义一组高阶圣堂武士的不稳定度为 maxni=1|ai|,请你通过不限次数的传递灵能操作使得你控制的这一组高阶圣堂武士的不稳定度最小。

输入格式
本题包含多组询问。输入的第一行包含一个正整数 T 表示询问组数。

接下来依次输入每一组询问。

每组询问的第一行包含一个正整数 n,表示高阶圣堂武士的数量。

接下来一行包含 n 个数 a1,a2,···,an。

输出格式
输出 T 行。

每行一个整数依次表示每组询问的答案。

数据范围
1≤T≤3,3≤n≤300000,|ai|≤109,
每个评测用例的限制如下:

QQ截图20191205220735.png

输入样例1:
3
3
5 -2 3
4
0 0 0 0
3
1 2 3
输出样例1:
3
0
3
输入样例2:
3
4
-1 -2 -3 7
4
2 3 4 -8
5
-1 -1 6 -1 -1
输出样例2:
5
7
4
样例解释
样例一
对于第一组询问:
对 2 号高阶圣堂武士进行传输操作后 a1=3,a2=2,a3=1。答案为 3。
对于第二组询问:
这一组高阶圣堂武士拥有的灵能都正好可以让他们达到最佳战斗状态。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值