贪心算法

股票买卖 II

题目大意
给定一个长度为 N N N 的数组,数组中的第 i i i 个数字表示一个给定股票在第 i i i 天的价格。设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
输入格式
第一行包含整数 N N N,表示数组长度。
第二行包含 N N N 个不大于 10000 的正整数,表示完整的数组。
输出格式
输出一个整数,表示最大利润。
数据范围:1≤N≤105
输入样例

6
7 1 5 3 6 4

输出样例

7

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

#include <cstdio>
using namespace std;
const int maxn = 1e5 + 7;
int a[maxn];
int main()
{
    int n;
    scanf("%d",&n);
    for(int i = 0; i < n; i++) scanf("%d",&a[i]);
    int res = 0;
    for(int i = 1; i < n; i++)
        if(a[i]-a[i-1] > 0) res += a[i] - a[i-1];
    printf("%d\n",res);
    return 0;
}

货仓选址

题目大意
在一条数轴上有 N N N 家商店,它们的坐标分别为 A 1 A_1 A1~ A N A_N AN
现在需要在数轴上建立一家货仓,每天清晨,从货仓到每家商店都要运送一车商品。
为了提高效率,求把货仓建在何处,可以使得货仓到每家商店的距离之和最小。
输入格式
第一行输入整数 N N N
第二行N个整数 A 1   A N A_1~A_N A1 AN
输出格式
输出一个整数,表示距离之和的最小值。
数据范围:1≤ N N N≤100000
输入样例

4
6 2 9 1

输出样例

12 

n n n 个点从小到大排序,找到中点,最后求绝对值之和,要防止爆 i n t int int

#include <cstdio>
#include <algorithm>
using namespace std;
int a[100007];
int main()
{
    int n;
    scanf("%d",&n);
    for(int i = 1; i <= n; i++) scanf("%d",&a[i]);
    sort(a+1, a + 1 + n);
    int mid = a[n+1>>1];
    long long ans = 0;
    for(int i = 1; i <= n; i++)
        ans += abs(mid - a[i]);
    printf("%d\n",ans);
    return 0;
}

糖果传递

题目大意
n n n 个小朋友坐成一圈,每人有 a [ i ] a[i] a[i] 个糖果。每人只能给左右两人传递糖果。每人每次传递一个糖果代价为1。求使所有人获得均等糖果的最小代价。
输入格式
第一行输入一个正整数 n n n,表示小朋友的个数。
接下来 n n n 行,每行一个整数 a [ i ] a[i] a[i],表示第i个小朋友初始得到的糖果的颗数。
输出格式
输出一个整数,表示最小代价。
数据范围: 1 ≤ n ≤ 1000000 1≤n≤1000000 1n1000000 数据保证一定有解。
输入样例

4
1
2
5
4

输出样例

4

在这里插入图片描述

#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 7;
int a[maxn],c[maxn];
int main()
{
    int n; ll sum = 0;
    scanf("%d",&n);
    for(int i = 1; i <= n; i++) scanf("%d",&a[i]),sum += a[i];
    ll ave = sum / n;
    
    for(int i = 2; i <= n; i++) c[i] = c[i-1] + ave - a[i];
    
    sort(c+1,c+1+n);
    ll ans = 0;
    for(int i = 1; i <= n; i++) ans += abs(c[i] - c[n+1>>1]);
    printf("%lld\n",ans);
    return 0;
}

雷达设备

题目大意
假设海岸是一条无限长的直线,陆地位于海岸的一侧,海洋位于另外一侧。每个小岛都位于海洋一侧的某个点上。
雷达装置均位于海岸线上,且雷达的监测范围为 d d d,当小岛与某雷达的距离不超过 d d d 时,该小岛可以被雷达覆盖。
我们使用笛卡尔坐标系,定义海岸线为x轴,海的一侧在x轴上方,陆地一侧在 x x x 轴下方。
现在给出每个小岛的具体坐标以及雷达的检测范围,请你求出能够使所有小岛都被雷达覆盖所需的最小雷达数目。
输入格式
第一行输入两个整数 n n n d d d,分别代表小岛数目和雷达检测范围。
接下来 n n n 行,每行输入两个整数,分别代表小岛的 x , y x,y xy 轴坐标。
同一行数据之间用空格隔开。
输出格式
输出一个整数,代表所需的最小雷达数目,若没有解决方案则所需数目输出“-1”。
数据范围: 1 ≤ n ≤ 1000 1≤n≤1000 1n1000
输入样例

3 2
1 2
-3 1
2 1

输出样例

2

在这里插入图片描述

#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 1e3 + 7;
struct node{
    double l, r;  //左端点,右端点
    bool operator < (const node &t) const{
        return r < t.r; //右端点从小到大排序
    }
};
node a[maxn];
int vis[maxn];  // 1 表示当前区间存在, 0 表示被覆盖
double x[maxn], y[maxn];  //坐标x,y
int main()
{
    int n, d, ok = 0;
    scanf("%d%d",&n,&d);
    for(int i = 1; i <= n; i++) 
    {
        scanf("%lf%lf",&x[i],&y[i]);
        if(abs(y[i]) > d) ok = 1;
        a[i].r = x[i] + sqrt(d*d - y[i]*y[i]);
        a[i].l = x[i] - sqrt(d*d - y[i]*y[i]);
        vis[i] = 1;
    }
    if(ok) puts("-1");
    else
    {
        sort(a + 1, a + 1 + n);  //将所有区间按右端点排序
        int ans = 0;
        /***
        扫描每个线段
         1>如果上一个点不在区间内,则选右端点
         2>如果上一个点在区间内,则跳过
        ***/
        for(int i = 1; i <= n; i++)
            if(vis[i])
            {
                ans++;
                for(int j = i + 1; j <= n; j++)
                    if(a[j].l <= a[i].r) vis[j] = 0;
            }
        printf("%d\n",ans);
    }
    return 0;
}

付账问题

题目大意
几个人一起出去吃饭是常有的事。
但在结帐的时候,常常会出现一些争执。
现在有 n n n 个人出去吃饭,他们总共消费了 S S S 元。
其中第 i i i 个人带了 a i a_i ai 元。
幸运的是,所有人带的钱的总数是足够付账的,但现在问题来了:每个人分别要出多少钱呢?
为了公平起见,我们希望在总付钱量恰好为 S S S 的前提下,最后每个人付的钱的标准差最小。
这里我们约定,每个人支付的钱数可以是任意非负实数,即可以不是 1 分钱的整数倍。
你需要输出最小的标准差是多少。
标准差的介绍:标准差是多个数与它们平均数差值的平方平均数,一般用于刻画这些数之间的“偏差有多大”。
形式化地说,设第 i i i 个人付的钱为 b i b_i bi 元,那么标准差为 :
在这里插入图片描述
输入格式
第一行包含两个整数 n、S;
第二行包含 n 个非负整数 a1, …, an。
输出格式
输出最小的标准差,四舍五入保留 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

输出样例

0.0000

输入样例2

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

输出样例

0.7928

在这里插入图片描述

#include <cmath>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 5e5 + 7;
typedef long double ld;
int n,a[maxn];
int main()
{
    ld s;
    scanf(" %d%Lf",&n,&s);
    for(int i = 0; i < n; i++) scanf("%d",&a[i]);
    sort(a, a + n);  //从小到大排序
    ld res = 0, ave = s/n;  
    for(int i = 0; i < n; i++)
    {
        ld now = s/(n - i);  //剩余的人均摊
        /*** 
        若每个人带的钱都大于等于平均值时,标准差为0;
        但当某人带的钱少于平均值时,则使其付自己全部的钱,
        剩下的钱由剩下的人均摊,直到最后钱付清为止.
        ***/
        if(a[i] <= now) now = a[i]; 
        res += (now - ave)*(now - ave);
        s -= now;  //剩余平摊的钱
    }
    printf("%.4Lf\n",sqrt(res/n));
    return 0;
}

乘积最大

题目大意
给定 N N N 个整数 A 1 , A 2 , … A N A_1,A_2,…A_N A1,A2,AN。请你从中选出 K K K 个数,使其乘积最大。请你求出最大的乘积,由于乘积可能超出整型范围,你只需输出乘积除以 1000000009 的余数。
注意,如果 X X X < 0, 我们定义 X X X 除以 1000000009 的余数是负(− X X X)除以 1000000009 的余数,即: 0−((0− x x x)%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
输入样例

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

输出样例

-999999829
/***
step 1. 对Ai~An排序

step 2. 分类讨论:
    1) k == n, 直接算

    2) k < n , 再分类
        1) k为偶数, 结果必然为负,分类讨论:
            a. 负数有偶数个, 则 res >= 0
            b. 负数有奇数个, 则选偶数个奇数, 则 res >= 0

        2) k为奇数, 分类讨论:
            a. 所有数均为负数, res < 0
            b. 至少存在一个非负数,先选择最大的那个,然后再从k - 1个中选(k - 1 为偶数,回到上一种情况),res >= 0
***/
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const int mod = 1e9 + 9, maxn = 1e5 + 7;
int a[maxn], n, k;
int main()
{
    scanf("%d%d",&n,&k);
    for(int i = 1; i <= n; i++) scanf("%d",&a[i]);
    
    int ans = 1;
    sort(a + 1, a + 1 + n); //排序
    if(n == k)  //全选
        for(int i = 1; i <= n; i++) ans = ans*a[i]%mod;
    else
    {
        int l = 1, r = n, ok = 1;  // l:左指针 r:右指针 ok:符号位
        if(k&1)  //取奇数
        {
            ans = a[r--];  //取最右端的数,右指针左移
            k--;  //剩余要取的数
            if(ans < 0) ok = -1;  //全负
        }
        while(k)
        {
            ll lv = (ll)a[l]*a[l+1], rv = (ll)a[r]*a[r-1];
            /***
            需要注意的是:不可以写成(x * res) % mod,也不可以写成是 res % mod * x % mod
            因为x最大是 10^10,如果不先取模的话和res相乘的结果最大是 10^19,会爆long long
            ***/
            if(lv*ok > rv*ok)
            {
                l += 2;
                ans = lv%mod*ans%mod;
            }
            else
            {
                r -= 2;
                ans = rv%mod*ans%mod;
            }
            k -= 2;
        }
    }
    printf("%d\n",ans);
    return 0;
}

后缀表达式

题目大意
给定 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+−,则 “23+1−” 这个后缀表达式结果 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≤A_i≤10^9 109Ai109
输入样例

1 1
1 2 3

输出样例

4
/***
不存在减号:直接相加
  存在减号:找出 n 个数中的最大值,其余数取绝对值相加 
***/
#include <cstdio>
#include <cmath>
using namespace std;
const int maxn = 2e5 + 7;
typedef long long ll;
int a[maxn];
int main()
{
    int n, m;
    scanf(" %d%d",&n,&m);
    int k = n + m + 1;
    for(int i = 1; i <= k; i++) scanf("%d",&a[i]);
    ll res = 0;
    if(!m)  // 0 个减号
        for(int i = 1; i <= k; i++) res += a[i];
    else
    {
        int maxx = -1e9 - 1, minx = 1e9 + 1, maxi = 0, mini = 0;
        for(int i = 1; i <= k; i++)
        {
            if(a[i] > maxx) maxx = a[i], maxi = i;  //找最大值记录下标
            if(a[i] < minx) minx = a[i], mini = i;  //找最小值记录下标
        }
        res = maxx - minx;
        for(int i = 1; i <= k; i++) 
            if(i != maxi && i != mini) res += abs(a[i]);
    }
    printf("%lld\n",res);
    return 0;
}

灵能传输

题目大意
在游戏《星际争霸 II》中,高阶圣堂武士作为星灵的重要 A O E AOE AOE 单位,在游戏的中后期发挥着重要的作用,其技能”灵能风暴“可以消耗大量的灵能对一片区域内的敌军造成毁灭性的伤害。
经常用于对抗人类的生化部队和虫族的刺蛇飞龙等低血量单位。
你控制着 n n n 名高阶圣堂武士,方便起见标为 1 , 2 , ⋅ ⋅ ⋅ , n 1,2,⋅⋅⋅,n 1,2,,n
每名高阶圣堂武士需要一定的灵能来战斗,每个人有一个灵能值 a i a_i ai 表示其拥有的灵能的多少( a i a_i ai 非负表示这名高阶圣堂武士比在最佳状态下多余了 a i a_i ai 点灵能, a i a_i ai 为负则表示这名高阶圣堂武士还需要 − a i a_i ai 点灵能才能到达最佳战斗状态)。
现在系统赋予了你的高阶圣堂武士一个能力,传递灵能,每次你可以选择一个 i ∈ [ 2 , n − 1 ] i∈[2,n−1] i[2,n1],若 a i a_i ai≥0 则其两旁的高阶圣堂武士,也就是 i − 1 i−1 i1 i + 1 i+1 i+1 这两名高阶圣堂武士会从 i i i 这名高阶圣堂武士这里各抽取 a i a_i ai 点灵能;若 a i a_i ai<0 则其两旁的高阶圣堂武士,也就是 i − 1 , i + 1 i−1,i+1 i1,i+1 这两名高阶圣堂武士会给 i i i 这名高阶圣堂武士 − a i a_i ai 点灵能。
形式化来讲就是 a i − 1 + = a i , a i + 1 + = a i , a i − = 2 a i a_i−1+=a_i,a_i+1+=a_i,a_i−=2a_i ai1+=ai,ai+1+=ai,ai=2ai
灵能是非常高效的作战工具,同时也非常危险且不稳定,一位高阶圣堂武士拥有的灵能过多或者过少都不好,定义一组高阶圣堂武士的不稳定度为 m a x n i = 1 ∣ a i ∣ max{^n}{_{i=1}}|a_i| maxni=1ai,请你通过不限次数的传递灵能操作使得你控制的这一组高阶圣堂武士的不稳定度最小。
输入格式
本题包含多组询问。输入的第一行包含一个正整数 T T T 表示询问组数。
接下来依次输入每一组询问。
每组询问的第一行包含一个正整数 n n n,表示高阶圣堂武士的数量。
接下来一行包含 n n n 个数 a 1 , a 2 , ⋅ ⋅ ⋅ , a n a_1,a_2,⋅⋅⋅,a_n a1,a2,,an
输出格式
输出 T T T 行。
每行一个整数依次表示每组询问的答案。
数据范围: 1 ≤ T ≤ 3 , 3 ≤ n ≤ 300000 , ∣ a i ∣ ≤ 1 0 9 1≤T≤3,3≤n≤300000,|a_i|≤10^9 1T3,3n300000,ai109,
每个评测用例的限制如下:
在这里插入图片描述
输入样例

3
3
5 -2 3
4
0 0 0 0
3
1 2 3

输出样例

3
0
3

样例解释
对于第一组询问:
对 2 号高阶圣堂武士进行传输操作后 a1=3,a2=2,a3=1。答案为 3。
对于第二组询问:
这一组高阶圣堂武士拥有的灵能都正好可以让他们达到最佳战斗状态。
题目输出的答案就是操作过后最大值最小是多少?

/***
 a[i-1] += a[i];  ==> a[i-1] + a[i] = s[i]
 a[i] -= 2a[i];   ==> a[i] - 2a[i] = s[i-1] - s[i] = -a[i]  ==> s[i] - a[i] = s[i-1]
 a[i+1] += a[i];  ==> a[i+1] + a[i] = s[i] + a[i] = s[i+1]
 求序列最大值最小
 a[0]     a[1]    a[2]    a[3]
  0        5       -2      3
 s[0]     s[1]    s[2]    s[3]
  0        5        3      6
***/
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn = 3e5 + 7;
ll a[maxn], sum[maxn], f[maxn];
bool st[maxn];
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    { 
        memset(st, true, sizeof st);
        int n;
        scanf("%d",&n);
        /***
        除了 s[0] 和 s[n] 以外 1 ~ n的任何 s[i] 可以进行相互交互从而得到一个有序的序列
        而a[i] = s[i] - s[i-1], 意味着可以通过交换 s[i] 的方式得到灵能传输后最终结果
        ***/
        sum[0] = 0;  //前缀和
        for(int i = 1; i <= n; i++) scanf("%lld",&a[i]),sum[i] = sum[i-1] + a[i];
        //左端点小于右端点 
        ll l = sum[0], r = sum[n];
        if(l > r) swap(l,r);
        sort(sum, sum + n + 1);
        /***
        极小值在极大值左边
        该点要求我们在后续选点的时
        应s[0]向左取 s[n]向右取 因为只有这样才能取得两边的极值
        ***/
        for(int i = 0; i <= n; i++)
            if(sum[i] == l) l = i, i = n;
        for(int i = 0; i <= n; i++)
            if(sum[i] == r ) r = i, i = n;
        //构造重叠部分最小的序列,最终的有序序列
        int s = 0, e = n;
        for(int i = l; i >= 0; i -= 2) f[s++] = sum[i],st[i] = false;
        for(int i = r; i <= n; i += 2) f[e--] = sum[i],st[i] = false;
        for(int i = 0; i <= n; i++)
            if(st[i]) f[s++] = sum[i];
        ll res = 0;
        for(int i = 1; i <= n; i++) res = max(res, abs(f[i] - f[i-1]));
        printf("%lld\n",res);
    }
    return 0;
}

Doing Homework again

题目大意
n n n 门作业,每门作业有个截止日期,超过截止日期未完成扣 a i a_i ai 分,问最少扣多少分?
输入格式
输入一个 T T T,表示有 T T T 组数据。
每组数据一个 n n n ( 1 ≤ n ≤ 1000 ) (1 \leq n \leq 1000) (1n1000),表示有 n n n 门数据。
第一行 n n n 个数,第 i i i 个数表示第 i i i 门作业的截止日期。
第二行 n n n 个数,第 i i i 个数表示第 i i i 门作业的分数。
输出格式
每组数据输出一个最少扣除的分数,每行对应一个数。
输入样例

2
3
3 3 3 
10 5 1
7
1 4 6 4 2 4 3
3 2 1 7 6 5 4

输出样例

0
5

将每组数据的 n n n 门作业按照分数从大到小排序,判断最迟期限前能否完成,开个数组记录一下。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn = 1e3 + 7;
struct node
{
    int t, s;
    bool operator < (const node &c) const{
        return s > c.s;
    }
};
node a[maxn];
int vis[maxn];
int main()
{
    int  T;
    scanf("%d",&T);
    while(T--)
    {
        int n;
        memset(vis,0,sizeof vis);  // 0 表示未用, 1 表示用了
        scanf("%d",&n);
        for(int i = 1; i <= n; i++) scanf("%d",&a[i].t);
        for(int i = 1; i <= n; i++) scanf("%d",&a[i].s);

        sort(a + 1, a + n + 1);
        int res = 0;
        for(int i = 1; i <= n; i++)
        {
            int now = a[i].t;  //当前作业最迟时间期限
            if(vis[now])  //当天被占用
            {
                while(now && vis[now]) now--;  //向前找
                if(now) vis[now] = 1;  //当前天数合法,标记使用
                else res += a[i].s;  //不合法
            }
            else vis[now] = 1;  //当天被占用
        }
        printf("%d\n",res);
    }
    return 0;
}

Protecting the Flowers

题目大意
N N N 头牛,把牛牵回棚的单程时间是 t t t,其余牛可以继续破坏 D i D_i Di个单位的物品,请编写程序求输出的最少破坏的物品是多少?
输入格式
第一行输入一个 N N N,表示有 N N N 头牛。
第二行到第 N + 1 N+1 N+1 行,每行两个数 T i T_i Ti D i D_i Di
输出格式
输出一个数字表示破坏的最少的物品。
输入样例

6
3 1
2 5
2 3
4 1
1 6

输出样例

86

贪心策略就是选择单位时间内破坏力大的先牵走。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn = 2e6 + 7;
struct node
{
    int t, d;
    bool operator < (const node &c) const{
        return 1.0*d/t > 1.0*c.d/c.t;
    }
};
node a[maxn];
int main()
{
    int n; ll sum = 0;
    scanf("%d",&n);
    for(int i = 1; i <= n; i++) scanf("%d%d",&a[i].t,&a[i].d),sum += a[i].d;
    ll res = 0;
    sort(a + 1, a + 1 + n);
    for(int i = 1; i <= n; i++)
    {
        sum -= a[i].d;
        res += sum*2*a[i].t;
    }
    printf("%lld\n",res);
    return 0;
}

结语

贪心算法就是说我们在处理问题的时候总是选择当前最优的策略,得到的是一个局部最优解,在解决问题的时候一定要保证无后效性,当前的状态不会影响未来的选择才能保证局部最优解最后是整体的最优解。换句话说就是所有贪心的题目都可以选择动态规划的方法,而动态规划却的问题却不能用贪心解决,个人做题感受,本蒟蒻的博客比较水,大佬勿喷!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

幸愉聊信奥

谢谢亲的支持,我会继续努力啦~

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

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

打赏作者

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

抵扣说明:

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

余额充值