简单贪心
求解的每一步都是最优,那么结果就是最优的。严格的贪心需要想出来这个最优的策略,但是简单贪心一般可以看出来,顺着题目的意思想就可以。
P1181 数列分段 Section I
思路:能分段就分段,不能的话就下一个。
#include<iostream>
using namespace std;
int main() {
int n, m, a;
cin >> n >> m;
int sum = 1;
int space = 0;
for (int i = 1; i <= n; i++) {
cin >> a;
if (space + a <= m) {
space += a;
}
else {
sum++;
space = a;
}
}
cout << sum;
return 0;
}
P1094 [NOIP2007 普及组] 纪念品分组
思路:排序,最大的找最小的,如果不行就自己分组。这个代码的巧妙之处是,定位l最小,r最大,++/--实现在数组里的左右移动。
代码:
#include<iostream>
#include<algorithm>
using namespace std;
int a[30005];
int main() {
int w, n;
cin >> w;
cin >> n;
int sum = 0;
for (int i = 0; i < n; i++) {
cin >> a[i];
}
sort(a, a + n);
int l = 0, r = n - 1;
while (l <= r) {
if (l == r) {
sum++; break;
}
if (a[r] + a[l] <= w) {
r--; l++;
sum++;
}
else {
r--;
sum++;
}
}
cout << sum;
return 0;
}
贪心背包
不是动态规划,是不完全背包。
P1208 [USACO1.3] 混合牛奶 Mixing Milk
思路:
就一直买单价最低的牛奶就可以。
#include<iostream>
#include<algorithm>
using namespace std;
struct milkseller {
int p;
int a;
}milk[5005];
bool cmp(milkseller a, milkseller b ) {
return a.p < b.p;
}
int main() {
int n, m;
cin >> n >> m;
int money = 0;
for (int i = 1; i <= m; i++) {
cin >> milk[i].p >> milk[i].a;
}
sort(milk + 1, milk + m + 1, cmp);
for (int i = 1; i <= m; i++) {
if (milk[i].a <= n) {
money += milk[i].a * milk[i].p;
n = n - milk[i].a;
}
else {
money +=n * milk[i].p;
n = 0; break;
}
}
cout << money;
return 0;
}
区间贪心
P1803 凌乱的yyy / 线段覆盖
思路1:求不重合的线段,第一种方法是始终找最大的左端点:让线段排序的时候左端点从大到小排,相同左端点的让其右端点从小到大排序,这样的话可以避免重合;然后从右边往左边排序,在不会部分重合的情况下比较下一条线段的右端点和现在的最左端点就可以。
为什么这样可以避免重合呢?因为这时候是从右边向左边排序,每一次左端点最大的此时是最靠近的所以选最大的左端点的,选右端点小的存粹是为了代码计算的时候一次就成,如果此时最短的线段都放不下,那就不用想相同左端点长度更长的了。
#include<iostream>
#include<algorithm>
using namespace std;
const int MAX = 1000005;
struct inteval{
int x,y;
}I[MAX];
bool cmp(inteval a, inteval b) {
if (a.x != b.x) return a.x > b.x;//左端点从大到小
else return a.y <= b.y;//右端点从小到大
}
int main() {
int n;
cin >> n;
for (int i = 0; i < n; i++) {
cin >> I[i].x >> I[i].y;
}
sort(I, I + n, cmp);
int ans = 1; int lastx = I[0].x;
for (int i = 1; i < n; i++) {
if (lastx >= I[i].y) {
ans++;
lastx = I[i].x;
}
}
cout << ans;
return 0;
}
思路2:找最小的右端点。cmp比较函数先让右端点不同的从小到大排序,如果右端点相同的话,就让左端点从小到大排序,这样也可以避免重合。去掉重合后的思路和思路1一样。
这时候为什么没有重合?现在是从左向右排序,每次最左边的一定是右端点最小的,此时右端点最小的是最靠近左边的。找此时左端点小的,是因为想先找短的线段,毕竟固定了一个右端点之后,上一个右端点到现在的右端点之间智能放一条线段,最短的能放就够了。
代码如下:
#include<iostream>
#include<algorithm>
using namespace std;
const int MAX = 1000005;
struct inteval{
int x,y;
}I[MAX];
bool cmp(inteval a, inteval b) {
if (a.y != b.y) return a.y < b.y;//右端点从小到大
else return a.x <= b.x;//左端点从小到大
}
int main() {
int n;
cin >> n;
for (int i = 0; i < n; i++) {
cin >> I[i].x >> I[i].y;
}
sort(I, I + n, cmp);
int ans = 1; int lasty = I[0].y;
for (int i = 1; i < n; i++) {
if (lasty <= I[i].x) {
ans++;
lasty = I[i].y;
}
}
cout << ans;
return 0;
}
总结: 贪心是用来解决一类最优化问题,并希望由局部最优策略来推得全局最优策略的算法。贪心算法适用的问题一定满足最优子结构性质,即一个问题的最优解可以由子问题的最优解构造出来。