• 【洛谷】训练场_贪心篇(不全)


    笔者认为:贪心算法(greedy algorithm),指对事情的每一步都选择最优解解决,因此常用做求最值问题。

    P1090 合并果子

    题意:

    多多有n堆果子,每堆果子的数目可能不一样(ai),多多想要把果子合并成一堆,求多多最少花费的体力值。

    例如有 33 种果子,数目依次为 1 , 2 , 9 。可以先将 1 、 2 堆合并,新堆数目为 3 ,耗费体力为3 。接着,将新堆与原先的第三堆合并,又得到新的堆,数目为 12 ,耗费体力为 12 。所以多多总共耗费体力 = 3 + 12 = 15。可以证明 15为最小的体力耗费值。

    分析:

    只要在每次合并中,合并的是一堆里数目最少的两堆就可以得到最优解。因此一开始想到 是每次都排列一下数组,然后取最小的两个数。结果TLE了5个点。百度了一下sort()的时间复杂度是O(n*log2(n)),整个算法就是 n * nlog2(n) >= n2

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<algorithm>
     4 using namespace std;
     5 const int maxn = 1e5 + 2;
     6 long long num[maxn];
     7 long long n, ans;
     8 
     9 int main()
    10 {
    11     while(cin >> n){
    12         ans = 0;
    13         for(int i = 0; i < n; i++) cin >> num[i];
    14 
    15         for(int i = 0; i < n - 1; i++){
    16             sort(num + i, num + n);
    17 
    18             ans += num[i] + num[i+1];
    19             num[i+1] = num[i] + num[i+1];
    20             num[i] = 0;
    21         }
    22         cout << ans << endl;
    23     }
    24     return 0;
    25 }
    TLE代码

    笔者的思想是,要使每次输入都让数组的值都从小到大排列,除了以上做法还有另外的做法吗?答案是有的,使用priority_queue(优先队列),不过priority_queue取出的是最大值,因此还需要转换一下。

    // 声明一个从大到小取出数值的优先队列

    priority_queue<int> que;

    // 声明一个从小到大取出数值的优先队列(P77)

    priority_queue<int, vector<int>, greater<int> > que;

    简单分析下:

    先看下priority_queue的声明:

     

    template <class T, class Container = vector<T>,

      class Compare = less<typename Container::value_type> > class priority_queue;

    Vector名为“容器”,可存放任意类型的动态数组(好,这个很容易理解);

    让我最不解的是,less 与 greater;

    二者可以简单理解为,前者是从小到大的排列(less),后者是从大到小的排列(greater),而他俩恰好在priority_queue里的效果相反了。

    这时忽然想到queue的特性是“先进先出”,因此,less是把一串数字从小到大地存储进去,再使用top(顶部),所以取出的是队列里最大的数,于是greater正好和less相反了。(这个top很有灵性呀)

    百度一下priority_queue的时间复杂度:

    1) push() O(log N)

    2) top() O(1)

    3) pop() O(log N)

    4) empty() O(1)

    5) size() O(1)

    整个算法下来的时间复杂度是 n * log n,比第一个算法少了一个 n,AC了/

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<queue>
     4 #include<vector>
     5 using namespace std;
     6 
     7 priority_queue<int, vector<int>, greater<int> > que;
     8 int ans;
     9 
    10 int main()
    11 {
    12     int n;
    13     while(cin >> n) {
    14         int num; ans = 0;
    15         for(int i = 0; i < n; i++) cin >> num, que.push(num);
    16 
    17         while(que.size() >= 2){
    18             int min1 = que.top(); que.pop();
    19             int min2 = que.top(); que.pop();
    20             ans += min1 + min2;
    21             que.push(min1 + min2);
    22         }
    23         cout << ans << endl;
    24     }
    25     return 0;
    26 }
    AC代码

    收获:priority_queue优先队列的使用

    下一题:

    P1181 数列分段Section Ⅰ

    题意:

    对于给定的一个长度为N的正整数Ai,现要将其分成连续的若干段,并且每段和不超过M(可以等于M),问最少能将其分成多少段使得满足要求。

    比如, N = 5, M = 6, 数组A = { 4, 2, 4, 5, 1 } ,最终结果是 3,因为可以划分为[4][2,4][5,1]

    分析:

    难度是入门难度——水题。

    先排序,然后以最大为主,找最小的。

     1 #include<cstdio>
     2 #include<iostream>
     3 #include<algorithm>
     4 using namespace std;
     5 
     6 const int maxn = 1e6 + 2;
     7 int n, m;
     8 int num[maxn];
     9 int ans;
    10 
    11 int main()
    12 {
    13     while(cin >> n >> m){
    14         ans = 0;
    15         for(int i = 0; i < n; i++) cin >> num[i];
    16         sort(num, num+n);
    17 
    18         int a = 0, b = n-1;
    19         while(a <= b){
    20             int tot = 0;
    21             tot += num[b];
    22             b--;
    23             if(a > b) break;
    24             while(1){
    25                 tot += num[a];
    26                 if(tot > m) break;
    27                 a++;
    28             }
    29             ans++;
    30         }
    31 
    32         cout << ans << endl;
    33     }
    34     return 0;
    35 }
    WA代码

    结果一提交:3个WA,1个AC,1个RE

    (一脸问号)

    看了一个WA点的输出比我输出还大。

    ……

    噢~原来不能改动数组值的顺序,还真是水题啊!

     1 #include<cstdio>
     2 #include<iostream>
     3 using namespace std;
     4 
     5 const int maxn = 1e6 + 2;
     6 int n, m;
     7 int num[maxn];
     8 int ans, tot;
     9 
    10 int main()
    11 {
    12     while(cin >> n >> m){
    13         tot = 0; ans = 1;
    14         for(int i = 0; i < n; i++) cin >> num[i];
    15 
    16         for(int i = 0; i < n; i++){
    17             tot += num[i];
    18             if(tot > m) { ans++, tot = num[i]; }
    19         }
    20         cout << ans << endl;
    21     }
    22     return 0;
    23 }
    AC代码

    但我为什么会理解错题意呢?得好好反思一下。

    收获:并没有quq;

    下一题!

    P1208[USACO1.3]混合牛奶 Mixing Milk

    题意:

    买牛奶,给出需要牛奶的总数n与提供你购买选择的m位农民,下面n+1行就是每位农名可以卖给你的牛奶总量以及单价。求达到n时最少的花费。

    分析:

    水题。只要从小到大排序下单价购买就Ok。

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<algorithm>
     4 using namespace std;
     5 typedef long long LL;
     6 const int maxn = 5005;
     7 int n, m, ans;
     8 struct node {
     9     int p;
    10     int a;
    11 }milk[maxn];
    12 
    13 bool cmp(node a, node b)
    14 {
    15     return a.p < b.p;
    16 }
    17 
    18 int main()
    19 {
    20     while(cin >> n >> m){
    21         ans = 0;
    22         for(int i = 0; i < m; i++) cin >> milk[i].p >> milk[i].a;
    23 
    24         sort(milk, milk+m, cmp);
    25 
    26         for(int i = 0; i < m && n > 0; i++){
    27             if(n < milk[i].a) { ans += milk[i].p * n; n -= n; }
    28             else { ans += milk[i].p * milk[i].a; n -= milk[i].a; }
    29         }
    30 
    31         cout << ans << endl;
    32     }
    33     return 0;
    34 }
    AC代码

    收获:重温了下结构体与sort的运用。其实并没有收获什么quq

    下一题:

    P1223 排队接水

    题意:

    有n个人在一个水龙头前排队接水,假如每个人接水的时间为Ti,请编程找出这n个人排队的一种顺序,使得n个人的平均等待时间最小。

    分析:

    刚开始还没看懂题目,因为搞不懂排队顺序那个输出是什么意思。搞懂了就清楚了。

    第一个3表示的是原数组的第三个数,以此类推。

    水题,求最少的平均等待时间,当然是接水时间最少的人先排了,所以从小到达排列,然后就是简单的数学问题了。

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<algorithm>
     4 using namespace std;
     5 const int maxn = 1005;
     6 int n;
     7 double ans;
     8 struct node{
     9     int d;
    10     int sc;
    11 }num[maxn];
    12 
    13 bool cmp(node a, node b)
    14 {
    15     return a.sc < b.sc;
    16 }
    17 
    18 int main()
    19 {
    20     while(cin >> n){
    21         ans = 0;
    22         for(int i = 0; i < n; i++){
    23             cin >> num[i].sc;
    24             num[i].d = i+1;
    25         }
    26         sort(num, num+n, cmp);
    27 
    28         for(int i = 0; i < n; i++) ans += num[i].sc*(n-i-1);
    29 
    30         for(int i = 0; i < n; i++){
    31             if(i != 0) cout << ' ';
    32             cout << num[i].d;
    33         }
    34         cout << endl;
    35         printf("%.2f
    ", ans*1.0/n);
    36     }
    37     return 0;
    38 }
    AC代码

    收获:并没有quq

    下一题:

    P1094纪念品分组

    题意:

    元旦快到了,校学生会让乐乐负责新年晚会的纪念品发放工作。为使得参加晚会的同学所获得 的纪念品价值相对均衡,他要把购来的纪念品根据价格进行分组,但每组最多只能包括两件纪念品, 并且每组纪念品的价格之和不能超过一个给定的整数。为了保证在尽量短的时间内发完所有纪念品,乐乐希望分组的数目最少。

    你的任务是写一个程序,找出所有分组方案中分组数最少的一种,输出最少的分组数目。

    分析:

    题解跟我上面做数列分段那题的第一思路是一样的,而且还更简单!水题!

     1 #include<cstdio>
     2 #include<iostream>
     3 #include<algorithm>
     4 using namespace std;
     5 const int maxn = 3e5 + 5;
     6 int num[maxn];
     7 int w, n;
     8 int ans;
     9 int a, b, tot;
    10 
    11 int main()
    12 {
    13     while(cin >> w >> n){
    14         ans = 0;
    15         for(int i = 0; i < n; i++) cin >> num[i];
    16         sort(num, num+n);
    17 
    18         a = 0; b = n-1;
    19         while(a <= b){
    20             tot = num[b];
    21             b--;
    22             tot += num[a];
    23             if(tot <= w) a++;
    24             ans++;
    25         }
    26         cout << ans << endl;
    27     }
    28     return 0;
    29 }
    AC代码

    收获:并没有qwq

    下一题:

    P1803凌乱的yyy/线段覆盖

    题意:

    现在各大oj上有n个比赛,每个比赛的开始、结束的时间点是知道的。

    yyy认为,参加越多的比赛,noip就能考的越好(假的)

    所以,他想知道他最多能参加几个比赛。

    由于yyy是蒟蒻,如果要参加一个比赛必须善始善终,而且不能同时参加2个及以上的比赛。

    分析:

    题目写得很清楚了,线段覆盖题,经典的贪心问题,熟悉《挑战程序设计竞赛》(白书)这题也只能算水题了。贪心方法就是选最早结束的比赛。

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<algorithm>
     4 using namespace std;
     5 const int maxn = 1e7 + 2;
     6 int n, ans, t;
     7 struct node{
     8     int f;
     9     int s;
    10 }num[maxn];
    11 
    12 bool cmp(node a, node b)
    13 {
    14     return a.s < b.s;
    15 }
    16 
    17 int main()
    18 {
    19     while(cin >> n){
    20         ans = t = 0;
    21         for(int i = 0; i < n; i++) cin >> num[i].f >> num[i].s;
    22 
    23         sort(num, num+n, cmp);
    24 
    25         for(int i = 0; i < n; i++){
    26             if(t <= num[i].f){
    27                 ans++;
    28                 t = num[i].s;
    29             }
    30         }
    31 
    32         cout << ans << endl;
    33     }
    34     return 0;
    35 }
    AC代码

    收获:贪心经典题目的回顾。

    下一题:

    P1031均分纸牌

    题意:

    有N堆纸牌,编号分别为 1,2,…,N。每堆上有若干张,但纸牌总数必为N的倍数。可以在任一堆上取若干张纸牌,然后移动。

    移牌规则为:在编号为1堆上取的纸牌,只能移到编号为2的堆上;在编号为N的堆上取的纸牌,只能移到编号为N−1的堆上;其他堆上取的纸牌,可以移到相邻左边或右边的堆上。

    现在要求找出一种移动方法,用最少的移动次数使每堆上纸牌数都一样多。

    例如N=4,4堆纸牌数分别为:

    9 8 17 6

    移动3次可达到目的:

    从③4张牌放到 ④(9,8,13,10)-> 从 ③ 取3张牌放到 ②(9,11,10,10)-> 从 ② 取1张牌放到①(10,10,10,10)。

    分析:

    最后牌要一样多,所以最先想到平均数;

    这时我们可以知道每堆牌是否已经达到目标;

    这里需要注意的是,我们怎么判断那个已经达到目标的牌堆里还要不要移动呢?

    比如这个例子:

    3

    4 10 16

    这时候我们可以用到求最小子序列那题的思想,把每堆减去平均数就可以达到一下效果:

    -6 0 6

    一直加,加到非0状态就加一次,遍历完后,即可得到一个AC。

     1 #include<iostream>
     2 #include<cstdio>
     3 using namespace std;
     4 const int maxn = 105;
     5 int num[maxn];
     6 int n, tot, ans;
     7 
     8 int main()
     9 {
    10     while(cin >> n){
    11         ans = tot = 0;
    12         for(int i = 0; i < n; i++){
    13             cin >> num[i];
    14             tot += num[i];
    15         }
    16 
    17         int ave = tot / n;
    18         for(int i = 0; i < n; i++) num[i] -= ave;
    19         for(int i = 0; i < n-1; i++){
    20             if(num[i] == 0) continue;
    21             else {
    22                 num[i+1] += num[i];
    23                 ans++;
    24             }
    25         }
    26         cout << ans << endl;
    27     }
    28     return 0;
    29 }
    AC代码

    收获:多了一道需要注意的题目。

    下一题:

    P1080国王游戏

    省选/提高题。缺乏经验,先不写了。

    最后小结:

    洛谷上普及难度的题笔者是基本可以应付了,感觉是比入门难度还要灵活一点的题目,通常数据不大,思维比较灵活,只要头脑不犯晕基本没问题。

    五月第一场训练结束/


    感谢你能看到这里,笔者虽然经验不多,但正为成为一名合格的ACMer努力着,所以笔录中可能会有很多小毛病,你的纠错会是我进步的巨大推动器。

    再次感谢你。

    最后祝你身体健康,因为笔者刚到五月就感冒了quq。

  • 相关阅读:
    基于python批量获取Url
    记一次tp5.0.11rce
    centOS 6.2 x64系统上安装 oracle 11g R2 x64
    用xmanager连接Linux的配置步骤
    0级备份和全备
    配置EM遇到的问题
    转:如何迁移oracle 队列表
    oracle audit
    VIEWS for Oracle object privileges
    Oracle 脚本
  • 原文地址:https://www.cnblogs.com/Ayanowww/p/10805453.html
Copyright © 2020-2023  润新知