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

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

猜你喜欢

转载自www.cnblogs.com/Ayanowww/p/10805453.html
今日推荐