前言

有人说贪心算法是最简单的算法,原因很简单:你我其实都很贪,根本不用学就知道怎么贪。
有人说贪心算法是最复杂的算法,原因也很简单:这世上会贪的人太多了,哪轮到你我的份?

0.贪心入门

顾名思义,贪心算法总是作出在当前看来最好的选择。也就是说贪心算法并不从整体最优考虑,它所作出的选择只是在某种意义上的局部最优选择。当然,希望贪心算法得到的最终结果也是整体最优的。虽然贪心算法不能对所有问题都得到整体最优解,但对许多问题它能产生整体最优解。如单源最短路径问题(SPFADijkstra),最小生成树问题等。在一些情况下,即使贪心算法不能得到整体最优解,其最终结果却是最优解的很好近似。

1.贪心经典例题之均分纸牌

【题目链接】
洛谷【1031】
【题目描述】
有 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)。
【输入格式】
N(N 堆纸牌,1 <= N <= 100)
A1 A2 … An (N 堆纸牌,每堆纸牌初始数,l<= Ai <=10000)
【输出格式】
所有堆均达到相等时的最少移动次数。
【输入样例】

4
9 8 17 6

【输出样例】

3

【题解】
从左到右依次扫描,大于平均数的就把多的部分给右边那堆,小于平均数的就从右边那堆拿过来,由左至右逐渐等于平均数。
【程序】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include<iostream>
#include<cstdio>
using namespace std;
int n,aid,a[105],tot=0,sum=0,you;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;++i)
{
scanf("%d",&a[i]);
tot+=a[i];
}
aid=tot/n;
//////
for(int i=1;i<=n-1;++i)
{
if(a[i]-aid<0)
{
a[i+1]+=a[i]-aid;
sum++;
}
if(a[i]-aid>0)
{
a[i+1]+=a[i]-aid;
sum++;
}
}
printf("%d",sum);
return 0;
}

2.贪心经典例题之导弹拦截(第二问)

【题目链接】
洛谷【1020】
【题目描述】
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。
输入导弹依次飞来的高度(雷达给出的高度数据是不大于30000的正整数),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
【输入格式】
一行,若干个正整数。
【输出格式】
2行,每行一个整数,第一个数字表示这套系统最多能拦截多少导弹,第二个数字表示如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
【输入样例】

389 207 155 300 299 170 158 65

【输出样例】

6
2

【题解】
贪心策略:用一个tmp[]数组储存每套系统拦截的最低的导弹高度,每次新来的导弹与每套系统进行比较,如果小于其中一套的最低高度,则用该套系统拦截;如果都不能拦截,则新建一套系统。
【程序】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include<iostream>
#include<cstdio>
#define M 500500
using namespace std;
int t=1,stot=1,tot=0;
int a[M],tmp[M];
int f[M];
bool b;
int main()
{
while(scanf("%d",&a[t])!=EOF)
{
b=1;
for(int i=1;i<=stot;++i)
{
if(a[t]<=tmp[i])
{
b=0;
tmp[i]=a[t];
break;
}
}
if(b) tmp[stot++]=a[t];
t++;
}
t--;stot--;
printf("%d",stot);
return 0;
}

【说明】
这里只是第二问的解法和程序,第一问的相关内容已删去

3.贪心水题之合并果子

【题目链接】
洛谷【1090】
【题目描述】
在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。多多决定把所有的果子合成一堆。
每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。可以看出,所有的果子经过n-1次合并之后,就只剩下一堆了。多多在合并果子时总共消耗的体力等于每次合并所耗体力之和。
因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力。假定每个果子重量都为1,并且已知果子的种类数和每种果子的数目,你的任务是设计出合并的次序方案,使多多耗费的体力最少,并输出这个最小的体力耗费值。
例如有3种果子,数目依次为1,2,9。可以先将1、2堆合并,新堆数目为3,耗费体力为3。接着,将新堆与原先的第三堆合并,又得到新的堆,数目为12,耗费体力为12。所以多多总共耗费体力=3+12=15。可以证明15为最小的体力耗费值。
【输入格式】
包括两行,第一行是一个整数n(1<=n<=10000),表示果子的种类数。第二行包含n个整数,用空格分隔,第i个整数ai(1<=ai<=20000)是第i种果子的数目。
【输出格式】
包括一行,这一行只包含一个整数,也就是最小的体力耗费值。输入数据保证这个值小于$2^31$。
【输入样例】

3
1 2 9

【输出样例】

15

【题解】
贪心策略:每次将最小的两堆果子合并。。。
【程序】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include<cstdio>
#include<iostream>
#include<queue>
using namespace std;
int n,x,ans=0,tmp;
priority_queue <int> q;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;++i)
{
scanf("%d",&x);
q.push(-x);
}
for(int i=1;i<n;++i)
{
tmp=q.top();
ans-=q.top();
q.pop();
tmp+=q.top();
ans-=q.top();
q.pop();
q.push(tmp);
}
printf("%d",ans);
return 0;
}

【说明】
这里用到了优先队列,书上有教程。。。

4.乱入例题之独木桥(个人觉得有点贪心的感觉)

【题目链接】
洛谷【1007】
【题目背景】
战争已经进入到紧要时间。你是运输小队长,正在率领运输部队向前线运送物资。运输任务像做题一样的无聊。你希望找些刺激,于是命令你的士兵们到前方的一座独木桥上欣赏风景,而你留在桥下欣赏士兵们。士兵们十分愤怒,因为这座独木桥十分狭窄,只能容纳一个人通过。假如有两个人相向而行在桥上相遇,那么他们两个人将无妨绕过对方,只能有一个人回头下桥,让另一个人先通过。但是,可以有多个人同时呆在同一个位置。
【题目描述】
突然,你收到从指挥部发来的信息,敌军的轰炸机正朝着你所在的独木桥飞来!为了安全,你的部队必须撤下独木桥。独木桥的长度为L,士兵们只能呆在坐标为整数的地方。所有士兵的速度都为1,但一个士兵某一时刻来到了坐标为0或L+1的位置,他就离开了独木桥。
每个士兵都有一个初始面对的方向,他们会以匀速朝着这个方向行走,中途不会自己改变方向。但是,如果两个士兵面对面相遇,他们无法彼此通过对方,于是就分别转身,继续行走。转身不需要任何的时间。
由于先前的愤怒,你已不能控制你的士兵。甚至,你连每个士兵初始面对的方向都不知道。因此,你想要知道你的部队最少需要多少时间就可能全部撤离独木桥。另外,总部也在安排阻拦敌人的进攻,因此你还需要知道你的部队最多需要多少时间才能全部撤离独木桥。
【输入格式】
第一行:一个整数L,表示独木桥的长度。桥上的坐标为1…L;
第二行:一个整数N,表示初始时留在桥上的士兵数目;
第三行:有N个整数,分别表示每个士兵的初始坐标。
【输出格式】
只有一行,输出两个整数,分别表示部队撤离独木桥的最小时间和最大时间。两个整数由一个空格符分开。
【输入样例】

4
2
1 3

【输出样例】

2 4

【说明】
初始时,没有两个士兵同在一个坐标。
数据范围N<=L<=1000。
【题解】
请仔细想想再看题解

当两个士兵相遇时,两人均掉头,但是也可以理解为两人换了个位置继续前进,所以最短时间就是离岸最近的士兵花的时间,最长时间就是离岸最远的士兵花的时间。
【程序】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include<cstdio>
#include<iostream>
#include<cmath>
using namespace std;
int n,l;
int x;
int maxx=0,minn=0;
int main()
{
scanf("%d%d",&l,&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&x);
maxx+=max(0,max(x,l-x+1)-maxx);
minn+=max(0,min(x,l-x+1)-minn);
}
printf("%d %d",minn,maxx);
return 0;
}

-1.贪心放弃

如果你实在学不会贪心,放弃吧少年。。。
-the end-撒花*\ ( ̄▽ ̄)/~