在大多数的比赛中,可以使用贪心思想解决的题目,都是属于简单题一类的,之所以简单并不是说想都不用想就可以求出答案来的,相反,有些题目甚至想破头皮也想不出来,但是一看解题报告,却发现写法相当简单,难的是正确的贪心策略想不到。所以,我们说,贪心题的关键时找一个正确、有效的策略,使得结果最优。如何去找呢?先不忙,首先应该确定的是有没有这样的策略?了解过贪心算法的人,应该知道并不是所有的问题都可以使用它来解决的,原因就是贪心仅仅关注了局部的最优解。所以,当我们判断一个问题能不能使用贪心策略解决时,可以参考如下规则:
1、整体可以化为局部。
2、局部存在最优解。
3、局部与局部之间满足无后效性(即当前所做策略不会影响以前的状态)。
4、局部最优解可以推出整体最优解。
如果确定一个问题可以使用贪心求解,那么该如何求解呢?其实这个过程并没有固定的算法,问题的关键是贪心策略的选择。所以,下面仅仅给出一般过程:
1、选取问题的某个状态作为初始状态,并以此作为出发点。
2、循环求解,在这个过程中,根据我们采取的贪心策略,求出局部最优解。
3、利用局部最优解求出整体最优解。
利用上面的知识,下面我们就实际操作一次,HDOJ:1789,时空转移(点击打开链接),题目如下:
Doing Homework again
Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 6734 Accepted Submission(s): 4023
Each test case start with a positive integer N(1<=N<=1000) which indicate the number of homework.. Then 2 lines follow. The first line contains N integers that indicate the deadlines of the subjects, and the next line contains N integers that indicate the reduced scores.
3 3 3 3 3 10 5 1 3 1 3 1 6 2 3 7 1 4 6 4 2 4 3 3 2 1 7 6 5 4
0 3 5
题意:
有n门课,每一门课有一个限定的时间和超过时间后罚的分数,然后假定每一天只能完成一门课,问如何安排时间去完成这些课,使得罚分最少并输出。
分析:
读完题目就知道,就是要求找出一种安排策略,让罚分最少。很明显,这里的策略是具有贪心性质的,那接下来的关键就是如何找出正确的策略了。首先,我们应该确定的是该从哪一个状态出发?但是,无论是哪一个状态,都涉及到分数和时间两个元素,这种情况下就无法清晰的判定哪一门课应该先做,如可能有一门课的期限短但罚分少,而另一门课的期限较长但罚分较多。所以,如果有一种策略能控制一个影响因素不变就好了。事实上,这样的策略是存在的,那就是由罚分去枚举天数,由于我们的最终目的就是要让总罚分最少,那我们就贪心的先考虑罚分较多的,然后每当考虑一门课,扫描期限内的天数(注意要从最后的期限往前枚举,给其它课尽可能的留下余地),如果能完成,就将该天标记,否则计入总罚分,以此类推。
源代码:
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
struct Node
{
int dl;
int scr;
} data[1005];
int vis[1005];
int cmp (Node a, Node b)
{
if (a.scr != b.scr)
return a.scr > b.scr;
else return a.dl < b.dl;
}
int main ()
{//freopen("sample.txt", "r", stdin);
int cas;
scanf ("%d", &cas);
while (cas--)
{
int n;
memset (vis, 0, sizeof(vis));
scanf ("%d", &n);
for (int i=0; i<n; ++i)
scanf ("%d", &data[i].dl);
for (int i=0; i<n; ++i)
scanf ("%d", &data[i].scr);
sort (data, data+n, cmp);
int ans = 0;
for (int i=0; i<n; ++i)
{
int j=data[i].dl;
for (; j>=1; --j) // 枚举每一个合法的天数
if(!vis[j])
{
vis[j] = 1;
break;
}
if(j == 0)
ans += data[i].scr;
}
printf ("%d\n", ans);
}
return 0;
}
遗憾的是,对于每一道贪心的题目,基本上没有太过相似的策略,这样的话就需要我们平时多练、多思考,做题的时候才能比较快速、准确的找出方法去应付题目中比较隐晦难懂条件,其它类似的题目还有,HDOJ:1009、1257、4803。