zoukankan      html  css  js  c++  java
  • 【洛谷】训练场_贪心篇(不全)

    笔者认为:贪心算法(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。

  • 相关阅读:
    70.BOM
    69.捕获错误try catch
    68.键盘事件
    523. Continuous Subarray Sum
    901. Online Stock Span
    547. Friend Circles
    162. Find Peak Element
    1008. Construct Binary Search Tree from Preorder Traversal
    889. Construct Binary Tree from Preorder and Postorder Traversal
    106. Construct Binary Tree from Inorder and Postorder Traversal
  • 原文地址:https://www.cnblogs.com/Ayanowww/p/10805453.html
Copyright © 2011-2022 走看看