笔者认为:贪心算法(greedy algorithm),指对事情的每一步都选择最优解解决,因此常用做求最值问题。
题意:
多多有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 }
笔者的思想是,要使每次输入都让数组的值都从小到大排列,除了以上做法还有另外的做法吗?答案是有的,使用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 }
收获:priority_queue优先队列的使用
下一题:
题意:
对于给定的一个长度为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 }
结果一提交: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 }
但我为什么会理解错题意呢?得好好反思一下。
收获:并没有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 }
收获:重温了下结构体与sort的运用。其实并没有收获什么quq
下一题:
题意:
有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 }
收获:并没有quq
下一题:
题意:
元旦快到了,校学生会让乐乐负责新年晚会的纪念品发放工作。为使得参加晚会的同学所获得 的纪念品价值相对均衡,他要把购来的纪念品根据价格进行分组,但每组最多只能包括两件纪念品, 并且每组纪念品的价格之和不能超过一个给定的整数。为了保证在尽量短的时间内发完所有纪念品,乐乐希望分组的数目最少。
你的任务是写一个程序,找出所有分组方案中分组数最少的一种,输出最少的分组数目。
分析:
题解跟我上面做数列分段那题的第一思路是一样的,而且还更简单!水题!

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 }
收获:并没有qwq
下一题:
题意:
现在各大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 }
收获:贪心经典题目的回顾。
下一题:
题意:
有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 }
收获:多了一道需要注意的题目。
下一题:
省选/提高题。缺乏经验,先不写了。
最后小结:
洛谷上普及难度的题笔者是基本可以应付了,感觉是比入门难度还要灵活一点的题目,通常数据不大,思维比较灵活,只要头脑不犯晕基本没问题。
五月第一场训练结束/
感谢你能看到这里,笔者虽然经验不多,但正为成为一名合格的ACMer努力着,所以笔录中可能会有很多小毛病,你的纠错会是我进步的巨大推动器。
再次感谢你。
最后祝你身体健康,因为笔者刚到五月就感冒了quq。