1.贪心算法的介绍
贪心贪心顾名思义就是得要 "贪" ,简单来说就是通过不断选择局部最优来得出全局最优。比如说我需要在尽可能少的纸币中找给客人112元,那我会选择1张100元、1张10元、两张1元,而不是给客人112张1元。我们可以很轻松的想到每一步的最优解而且最后的结果就是全局的最优秀。
但是在实际的解题中我们是很难证明通过这个局部最优最后的结果就是正确的,而且当题的难度上来时可能连贪心的策略都很难找到至少我现在是这样。(不知道后面我回过头来看这篇文章时会不会有新的感受可能我未来都和计算机没关系吧哈哈)而证明往往会用到一些数学的方法比如反证法和数学归纳法等等,但我现在都是梦到什么写什么。
这种类型的题目千变万化每个题目的贪心策略都千奇百怪的,而且还有可能与其他的算法来结合起来那难度就会上升了所以下面我就以几道题为例子,主要还得是看题量的积累吧。
2.例题
2.1货仓选址
这道题应该是没少在中学时期的数学题出现过,所以我可先把商铺的坐标排个序,我们把其中两个相邻的商店给抽象出来会发现它们的三种情况,我们假设左边的商店里仓库为a1, 右边的距离仓库为a2,商店之间的距离固定为L,仓库里商定的总距离为K那我们就可以得到:
因此显然第二种情况是最优秀的,但是题目没有要求仓库不能和商店重合,所以无论商店的数量是奇数还是偶数都成立,所以我们可以在中间相邻的两个商店间建一个仓库是奇数就直接建在上面:
cpp
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10;
int n;
int a[N];
int main()
{
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
sort(a + 1, a + 1 + n);
int l = 1, r = n;
long long ret = 0;
while (l < r)
{
ret += a[r] - a[l];
l++;
r--;
}
cout << ret << endl;
return 0;
}
2.2最大子段和
这道题除了可以用前缀和解决之外还可以使用贪心的思想,我们可以定义一个变量向后走,一个变量来统计最大的子段,当先向后走的变量变成负数时就重置这个变量因为这个变量变成负数了之后就可能是最优解了。
cpp
#include <iostream>
using namespace std;
const int N = 2e5 + 10;
int a[N];
int n;
int main()
{
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
int sum = 0, ret = -0x3f3f3f3f;
for (int i = 1; i <= n; i++)
{
sum += a[i];
ret = max(ret, sum);
if (sum < 0) sum = 0;
}
cout << ret << endl;
return 0;
}
2.3纪念品分组
这道题也是清朝老题了,贪心策略也很简单把数组从小到大排个序定义左右两个区间变量,当右边的指针指向的数加上左边的指针指向的数超过纪念品的最大上限时右边那个比较大的数是肯定要单独为一组的然后左边保持不动让右边减减,如果可以为一组的话就让左右指针相互靠近:
cpp
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 3e4 + 10;
int a[N];
int n, w;
int main()
{
cin >> w >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
sort(a + 1, a + 1 + n);
int ret = 0;
int l = 1, r = n;
while (l <= r)
{
if (a[l] + a[r] <= w)
{
l++;
r--;
}
else if (a[l] + a[r] > w)
{
r--;
}
ret++;
}
cout << ret << endl;
return 0;
}
2.4[NOIP 2008 普及组] 排座椅
这道题的贪心策略很难想,这里用了一个比较巧的方法,就是一开始就创建一个结构体分别记录下对应行数(或者列)以及该行(列)相邻说话的人的对数,然后对说话的对数从大到小排列,因为最后需要按从大到小的顺序分别输出行数或者列数,所以还需要重新按照行数或者列数来排个序,这就是为什么我们一开始还需要记录下它们所对应的行数或者列数:
cpp
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int n, m, k, l, d;
struct node{
int indix;
int cnt;
}a[N], b[N];
bool cmp1(node& x1, node& x2)
{
return x1.cnt > x2.cnt;
}
bool cmp2(node& x1, node& x2)
{
return x1.indix < x2.indix;
}
int main()
{
cin >> n >> m >> k >> l >> d;
for (int i = 1; i <= n; i++) a[i].indix = i;
for (int i = 1; i <= m; i++) b[i].indix = i;
while (d--)
{
int x1, y1, x2, y2;
cin >> x1 >> y1 >> x2 >> y2;
if (x1 == x2) b[min(y1, y2)].cnt++;
else a[min(x1, x2)].cnt++;
}
sort(a + 1, a + 1 + n, cmp1);
sort(b + 1, b + 1 + m, cmp1);
sort(a + 1, a + 1 + k, cmp2);
sort(b + 1, b + 1 + l, cmp2);
for (int i = 1; i <= k; i++) cout << a[i].indix << ' ';
cout << endl;
for (int i = 1; i <= l; i++) cout << b[i].indix << ' ';
cout << endl;
return 0;
}
2.5矩阵消除游戏
这道题需要用到二进制枚举,我们通过二进制枚举枚举出所有行数的选择情况,当st的二进制位为1时代表我们选择这行,列就用一个数组来存储分数,这里主要是和上题一样不好想到还可以使用二进制来枚举反正我肯定是看题解的我也好奇是怎么想到的,但代码本身不难写主要体现在思维的难度上:
cpp
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 20;
int a[N][N];
int n, m, k;
int col[N];
int calc(int x)
{
int ret = 0;
while (x)
{
x = x & (x - 1);
ret++;
}
return ret;
}
bool cmp(int x, int y)
{
return x > y;
}
int main()
{
cin >> n >> m >> k;
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++)
cin >> a[i][j];
int ret = 0;
for (int st = 0; st < (1 << n); st++)
{
int cnt = calc(st);
if (cnt > k) continue;
memset(col, 0, sizeof col);
int sum = 0;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
if ((st >> i) & 1) sum += a[i][j];
else col[j] += a[i][j];
}
}
sort(col, col + m, cmp);
for (int i = 0; i < min(k - cnt, m); i++) sum += col[i];
ret = max(ret, sum);
}
cout << ret << endl;
return 0;
}
完