【PAT】12 贪心

第十二章 贪心

AcWing 1521. 魔术卷

问题描述

分析

  • 本题相当于给我们两个数组,我们需要给两个数组中的元素配对,使得配对的元素之积之和最大。

  • 对于两个数组中的正数,排序后,第一个数组中的最大值和第二个数组中的最大值配对,第一个数组中的次大值和第二个数组中的次大值配对,…,这样得到的和最大;负数类似。

  • 相关数学知识:排序不等式,即:反序和 ≤ 乱序和 ≤ 顺序和。

代码

  • C++
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 100010;

int n, m;
int a[N], b[N];

int main() {
    
    scanf("%d", &n);
    for (int i = 0; i < n; i++) scanf("%d", &a[i]);
    scanf("%d", &m);
    for (int i = 0; i < m; i++) scanf("%d", &b[i]);
    
    sort(a, a + n);
    sort(b, b + m);
    
    int res = 0;
    for (int i = 0, j = 0; i < n && j < m && a[i] < 0 && b[j] < 0; i++, j++)
        res += a[i] * b[j];
    for (int i = n - 1, j = m - 1; i >= 0 && j >= 0 && a[i] > 0 && b[j] > 0; i--, j--)
        res += a[i] * b[j];
        
    printf("%d\n", res);
    
    return 0;
}

AcWing 1522. 排成最小的数字

问题描述

分析

在这里插入图片描述

代码

  • C++
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 10010;

int n;
string str[N];

int main() {
    
    cin >> n;
    for (int i = 0; i < n; i++) cin >> str[i];
    
    sort(str, str + n, [](const string &a, const string &b){
        return a + b < b + a;
    });
    
    string res;
    for (auto &p : str) res += p;
    
    int k = 0;
    while (k + 1 < res.size() && res[k] == '0') k++;
    
    printf("%s\n", res.substr(k).c_str());
    
    return 0;
}

AcWing 1553. 用 Swap(0, i) 操作进行排序

问题描述

分析

  • 本题是环状图的问题,对于每个数id,让其指向其下标。这样我们可以得到一个图,这个图很有特点,一定是由若干环组成的。

  • 下列演示了不同操作的影响:

在这里插入图片描述

  • 我们最终的目标是将所有的点变为自环。

  • 通过和0交换,可以分为两大类操作:

    (1)0与环内的点交换:① 和0后面一个点next交换,会让next变为自环,有效操作;② 和非0后面的一个点交换位置,会让0所在的环变为两个环,操作无意义;

    (2)0和环外的点交换:合成一个环,有效操作

  • 具体操作时,可以先找到0所在的环,让0和后面的一个点交换,让所有该环内的点变为自环;然后找到其他环,首先和0合并,然后再将合并后的环中的每个点变为自环。

代码

  • C++
#include <iostream>

using namespace std;

const int N = 100010;

int n;
int p[N];

int main() {
    
    scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        int id;
        scanf("%d", &id);
        p[id] = i;
    }
    
    int res = 0;
    for (int i = 1; i < n; ) {
        while (p[0]) swap(p[0], p[p[0]]), res++;
        while (i < n && p[i] == i) i++;
        if (i < n) swap(p[0], p[i]), res++;
    }
    
    printf("%d\n", res);
    
    return 0;
}

AcWing 1556. 月饼

问题描述

分析

  • 优先选择单位价值较大的月饼卖出即可。

代码

  • C++
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1010;

int n;
double m;

struct Cake {
    double p, w;
    
    bool operator< (const Cake &t) const {
        return w / p > t.w / t.p;
    }
} c[N];

int main() {
    
    cin >> n >> m;
    for (int i = 0; i < n; i++) cin >> c[i].p;
    for (int i = 0; i < n; i++) cin >> c[i].w;
    
    sort(c, c + n);
    
    double res = 0;
    for (int i = 0; i < n && m > 0; i++) {
        double r = min(m, c[i].p);
        m -= r;
        res += c[i].w / c[i].p * r;
    }
    
    printf("%.2lf\n", res);
    
    return 0;
}

AcWing 1603. 整数集合划分

问题描述

分析

  • 分类讨论:当有偶数个数据,则分成两个数据个数相同的集合,较大的数分到一个集合,较小的数分到另一个集合;当有奇数个数据,因为给定的数据都是正整数,分成两个集合,一个集合n/2+1个数据,这个集合放较大的数据,另一个集合放n/2个数据,放较小的数据。

代码

  • C++
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 100010;

int n;
int w[N];

int main() {
    
    scanf("%d", &n);
    for (int i = 0; i < n; i++) scanf("%d", &w[i]);
    
    sort(w, w + n);
    
    int s1 = 0, s2 = 0;
    for (int i = 0; i < n / 2; i++) s1 += w[i];
    for (int i = n / 2; i < n; i++) s2 += w[i];
    printf("%d %d\n", n % 2, s2 - s1);
    
    return 0;
}

AcWing 1618. 结绳

问题描述

分析

  • 本题的考点:哈夫曼树

  • 本题如果将两个绳子(长度分别为a、b)合并成一条绳子,则新的绳子长度为(a+b)/2。我们的目标是最终拼接成一条绳子的长度最大值。

  • 应该先将长度最小的两个绳子合并。因为绳子越靠前被合并,被折叠的次数越多,我们希望较长的绳子折叠的次数尽可能少,所以较长的绳子应该较后备拼接。

  • 首先我们可以按照绳子的长度升序排序,然后从前向后合并绳子,合并后的绳子的长度一定是所有绳子中的长度最小的,这是因为(a+b)/2 <= max(a, b)

代码

  • C++
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 10010;

int n;
double w[N];

int main() {
    
    cin >> n;
    for (int i = 0; i < n; i++) cin >> w[i];
    
    sort(w, w + n);
    
    for (int i = 1; i < n; i++) w[0] = (w[0] + w[i]) / 2;
    
    printf("%d\n", (int)w[0]);
    
    return 0;
}

AcWing 1517. 是否加满油

问题描述

分析

  • 首先按照距离起点的距离将加油站排序。

  • 考虑第一站(位置0处)是否存在加油站,如果不存在,则无法到达目的地。

  • 接着依次考虑每个加油站是否应该加油以及如果应该加油应该加多少油。

  • 当前加了一些油的加油站编号如果为i,考虑其如果在本站加满油可以到达的所有加油站,找出油价最低的一个加油站,假设编号为k

  • 如果加油站k的油价低于加油站i的价格,则在加油站i加一些油满足可以开到加油站k即可,然后考虑加油站k;否则直接在加油站i加满油即可,然后考虑加油站k

代码

  • C++
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 510;

int c_max, d, d_avg, n;  // 油箱最大容量,目标距离起点距离,单位汽油里程数,加油站总数
struct Stop {
    double p, d;  // 汽油单位价格,距离起点距离
    
    bool operator< (const Stop &t) const {
        return d < t.d;
    }
} s[N];

int main() {
    
    cin >> c_max >> d >> d_avg >> n;
    for (int i = 0; i < n; i++) cin >> s[i].p >> s[i].d;
    s[n] = {0, (double)d};  // 终点
    
    sort(s, s + n);
    
    if (s[0].d) {  // 说明起点不存在加油站
        puts("The maximum travel distance = 0.00");
        return 0;
    }
    
    double res = 0, oil = 0;  // 花费,每站剩余油量
    double d_max = c_max * d_avg;  // 装满油箱可以行驶的距离
    for (int i = 0; i < n; ) {
        int k = -1;
        for (int j = i + 1; j <= n && s[j].d - s[i].d <= d_max; j++)
            if (s[j].p < s[i].p) {  // 找到一个比当前所在加油站单价更低的加油站
                k = j;
                break;
            } else if (k == -1 || s[j].p < s[k].p) {
                k = j;
            }
        
        if (k == -1) {  // 说明在当前加油站i加满油也无法到达下一个加油站
            printf("The maximum travel distance = %.2lf\n", s[i].d + d_max);
            return 0;
        }
        
        if (s[k].p < s[i].p) {  // 此时在加油站i加一些油足够到加油站k即可
            res += ((s[k].d - s[i].d) / d_avg - oil) * s[i].p;
            oil = 0;
            i = k;
        } else {  // 说明在当前加油站i加满油可以达到的加油站的油价都比当前油价高
            res += (c_max - oil) * s[i].p;
            oil = c_max - (s[k].d - s[i].d) / d_avg;
            i = k;
        }
    }
    
    printf("%.2lf\n", res);
    
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值