贪心算法,或者说是贪心策略,就是企图用局部最优找出全局最优。
1.把解决问题的过程分为若干步
2.解决每一步时,都选择"当前看起来最优的"解法
3."希望"得到全局的最优解
8.1 简单贪心
8.1.1 货仓选址

根据贪心的思想,我们最希望把仓库建在中间位置。
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
const int N = 1e5 + 10;
int n;
long long a[N];
int main()
{
cin >> n;
for (int i = 1;i <= n;i++)
{
cin >> a[i];
}
sort(a + 1, a + 1 + n);
long long ret = 0;
for (int i = 1;i <= n;i++)
{
ret += abs(a[i] - a[(1 + n) / 2]);
}
cout << ret << endl;
return 0;
}
那么,这个贪心策略是否就是本题的最优解呢?下面开始证明:
这里就要用到数学上的绝对值不等式:|a-x| + |b - x| ≥ |a-b|

根据上面的出来的公式,这道题的代码还可以这么写:
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
const int N = 1e5 + 10;
int n;
long long a[N];
int main()
{
cin >> n;
for (int i = 1;i <= n;i++)
{
cin >> a[i];
}
sort(a + 1, a + 1 + n);
long long ret = 0;
/*for (int i = 1;i <= n;i++)
{
ret += abs(a[i] - a[(1 + n) / 2]);
}*/
for (int i = 1;i <= n / 2;i++)
{
ret += a[n + 1 - i] - a[i];
}
cout << ret << endl;
return 0;
}
8.1.2 最大子段和

根据贪心的思想,我们希望sum ≥ 0,就继续向后累加;sum < 0,就舍弃这一段,从下个位置开始重新累加。
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
const int N = 2e5 + 10;
int n;
long long a[N];
int main()
{
cin >> n;
for (int i = 1;i <= n;i++)
cin >> a[i];
long long ret = -1e6, sum = 0;
for (int i = 1;i <= n;i++)
{
sum += a[i];
ret = max(ret, sum);
if (sum < 0)
sum = 0;
}
cout << ret << endl;
return 0;
}
下面证明该贪心思想是正确的:

8.1.3 纪念品分组

贪心策略:在剩余物品中,取出最小值x和最大值y,如果x + y ≤ w,就放在一起;如果x + y > w,就把y单独放,x待定。
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 3e4 + 10;
int n, w;
int a[N];
int main()
{
cin >> w >> n;
for (int i = 1;i <= n;i++)
cin >> a[i];
sort(a + 1, a + 1 + n);
int i = 1, j = n;
long long cnt = 0;
while (i <= j)
{
if (a[i] + a[j] <= w)
i++;
cnt++;
j--;
}
cout << cnt << endl;
return 0;
}
8.2 公式法
细说的话,这里应该是推公式+排序。其中推公式就是寻找排序规则,排序就是在该排序规则下对整个对象排序。在解决某些问题时,当我们发现最终结果需要调整每个对象的先后顺序,那么就可以使用推公式的方式来得出排序规则,进而对整个对象进行排序。但是这里的证明过程会很麻烦(需要用到离散数学中的"全序关系"),后续题目中我们只要发现该题的最终结果需要排序,并且交换相邻两个元素的时候对其余元素不会产生影响,那么就可以推导出排序的规则,然后直接去排序,就不用证明了。
8.2.1 拼数

直接拼接,然后确定先后关系。ab > ba,a前b后;ab < ba,b前a后。
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 30;
int n;
string a[N];
bool cmp(string& x, string& y)
{
return x + y > y + x;
}
int main()
{
cin >> n;
for (int i = 1;i <= n;i++)
cin >> a[i];
sort(a + 1, a + 1 + n, cmp);
for (int i = 1;i <= n;i++)
cout << a[i];
return 0;
}
8.2.2 保卫花园


cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10;
int n;
struct node
{
int t;
int d;
}a[N];
bool cmp(node& x, node& y)
{
return x.t * y.d < y.t * x.d;
}
int main()
{
cin >> n;
for (int i = 1;i <= n;i++)
cin >> a[i].t >> a[i].d;
sort(a + 1, a + 1 + n, cmp);
long long sum = 0, t = 0;
for (int i = 1;i <= n;i++)
{
sum += a[i].d * t;
t += 2 * a[i].t;
}
cout << sum << endl;
return 0;
}
8.3 区间问题
区间问题是另一种比较经典的贪心问题。题目面对的对象是一个一个的区间,让我们在每一个区间上做出取舍。这种题目的解决方式一般就是按照区间的左端点或者是右端点排序,然后在排序区间上,根据题目要求制定出贪心策略,进而得到最优解。
8.3.1 线段覆盖

这道题的本质就是有若干个区间,让我们尽可能多的选择一些区间,使它们之间互不重叠。面对区间问题,第一步先排序。排序分为两种------左端点升序/降序 以及 右端点升序/降序。这里我们按照左端点升序排列。当区间重叠的时候,尽可能保留右端点较小的区间;当区间不重叠的时候,以下一个区间为基准,继续判断。
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e6 + 10;
int n;
struct node
{
int left;
int right;
}a[N];
bool cmp(node& x, node& y)
{
return x.left < y.left;
}
int main()
{
cin >> n;
for (int i = 1;i <= n;i++)
cin >> a[i].left >> a[i].right;
sort(a + 1, a + 1 + n, cmp);
int cnt = 1;
int r = a[1].right; //以第一个区间为基准,向后选择
for (int i = 2;i <= n;i++)
{
int left = a[i].left;
int right = a[i].right;
if (left < r) //重叠
{
r = min(r, right);
}
else
{
cnt++;
r = right;
}
}
cout << cnt << endl;
return 0;
}
8.3.2 雷达安装

这道题如果直接在二维平面内分析的话会十分复杂,因为我们要枚举x轴上所有雷达的位置,然后计算每座岛屿到雷达的距离。所以第一步也是最关键的一步,就是把二维转化为一维。针对一座岛屿,当我们想要覆盖这座岛屿的时候,x轴上雷达的极限位置应该在哪一段区间内。第二步,在所有区间中,找出互相重叠的一共有多少组。先根据左端点排序,互相重叠的区间是连续的。当两个区间重叠时,应该用右端点较小的区间继续往后找。不重叠时,以下一个区间为基准,继续往后找。
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
const int N = 1010;
int n;
double d;
struct node
{
double l;
double r;
}a[N];
bool cmp(node& x, node& y)
{
return x.l < y.l;
}
int main()
{
int cnt = 0;
while (cin >> n >> d, n && d)
{
cnt++;
bool flag = false;
for (int i = 1;i <= n;i++)
{
double x, y;
cin >> x >> y;
if (y > d)
flag = true;
double l = sqrt(d * d - y * y);
a[i].l = x - l;
a[i].r = x + l;
}
cout << "Case " << cnt << ": ";
if (flag)
cout << -1 << endl;
else
{
sort(a + 1, a + 1 + n, cmp);
int ret = 1;
double r = a[1].r;
for (int i = 2;i <= n;i++)
{
double left = a[i].l;
double right = a[i].r;
if (left <= r) //有重叠
{
r = min(r, right);
}
else
{
ret++;
r = right;
}
}
cout << ret << endl;
}
}
return 0;
}