本周学习内容主要是贪心,因为要顾及考试,所以看的题目数量不是很多,20道左右。不过涉及的题型很广,所以收获也不少。
贪心其实更像一种思路,就是求得局部子问题求最优解,然后各个子问题汇总最优解得到整体最优解。因此,如何确定分阶段,确定每一阶段的方法才是贪心最困难的部分。而也正是在这上面,经常会混入像优先队列(STL)、二分、树、并查集、搜索等等知识一起考察。这也正是我所说的,贪心更像思想的原因。
贪心的常规方法就是排序然后寻找自己想要的条件数据,不过,它的排序花样很多,例如,左/右端点排序,双要素排序等。而且,贪心有一些典型的问题,包括:子序列问题,最优装载问题,01背包问题,区间调度问题,货币找零问题,字典序最小问题,分发饼干问题等。这里就简单的提几个经典问题进行分析。
一.区间调度问题
区间调度问题主要是在排序的问题上,注意点是结束时间与下一个开始时间的交接点,然后根据问题输出。有时,会遇见一些对端点以及区间包含关系的特殊问题,如:选课(端点互不相交),早起时间安排(区间覆盖)等等,遇见变种的题目时,其核心思想还是排序与选择,只要根据题目要求,对端点的设置进行相应的限制(如不相等,或者更新最大值)就可以解决。
虽然这道题很典型且延展性很强,但我看的博客里类似的题型并不多,所以就不举例了。
二.子序列问题
子序列问题其实并不止子序列,它还包括了最长/短序列的构造问题,其问题的核心就是在对原序列的处理上,是字符串要去重?还是数组要计算和(公约数等等),这都可以看成对原序列进行不同处理而得到结果的情况,我个人就把他们放在一起组成一类问题了。
处理这类问题,其实就是经典的贪心思路,根据贪心的排序进行操作,操作或计数,或累加,或递归,极具变化,但其实不难理解。
例题:uva11039 设计建筑物
题目大意:对于一组数,选出绝对值递增且正负交替的最长序列长度。
题目分析:如我所说,经典贪心思路,先对数组绝对值排序,然后将相邻的同号数字视为一个子序列,统计完后直接输出序列个数。
代码:
#include<bits/stdc++.h>
using namespace std;
const int MAX=0X7fffff;
int a[MAX];
bool cmp(int a,int b)
{
return abs(a)<abs(b);
}
int main()
{
int t;
cin >> t;
for(int i=0;i<t;i++)
{
int n,dian,ans=1;
cin >> n;
memset(a,0,sizeof(a));
for (int i = 1; i <= n; i++)
cin >> a[i];
sort(a+1,a+n+1,cmp);
if(a[1]<0)
dian=-1;
else
dian=1;
for(int i=2;i<=n;i++)
{
if(a[i]*dian<0)
{
ans++;
dian = -dian;;
}
}
cout << ans << endl;
}
}
这就是一道很基础的贪心题,简洁而明了。
三.本周总结
本周遇见的题型其实并不少,虽然不算完全的符合我所列的经典题型。但都能看出他们相似的地方,比如ICPC World Finals A Azulejos 这道题,就是在子序列问题的基础上增加了一个不能大于后排高度的要求,实际上还是对序列的处理。还有LeetCode 870 Advantage Shuffle这道题,也是处理复杂的基础贪心题,只要建立两个数组,排序后按对应的方法交换就可以解答。如果对操作改变一点,像LeetCode 1775 Equal Sum Arrays With Minimum Number of Operations这个,就有独特的处理方法:即先确定有无解,(无解即数组1可能的最小值大于数组2可能的最大值),再挑选更简洁的方法(即大变小或小变大),对差值大的数进行更改,就能得到最小的更改次数了。而有的题,像codeforces 672C Recycling Bottles,其实质是对结点成图,求边权问题的贪心处理,这就是典型的贪心应用于别的知识点。
就像上面简述的几道题,我本周因为时间原因,对于大部分题目都是理清思路,搞清原理即过。A出代码的并不多,不过可以很清楚的看到,贪心是一个富有极强扩展性的解题方案,但我们更要考虑贪心的使用范围,不能一味强求最优解,因为贪心的原则是各个子问题无法产生回路干涉,如果两个子问题不断反复干涉,贪心的方法就只能放弃。
所以,本周虽然看了很多贪心的题,但对于贪心真正的使用我觉得只是凤毛麟角,更多的方法我应该在今后的学习中去努力尝试了。这周能给的代码不多,更多就是思路与感悟。那么下周继续努力吧。