贪心详解

来源:互联网 发布:易语言安卓远控源码 编辑:程序博客网 时间:2024/05/18 23:54

定义

贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。
贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。


咋一听貌似是动态规划的样子,这里既然提到了就说下这两者之间的区别,若新学者还未接触动态规划,可以到我的01背包初学篇中了解一下,或者在具体学习之后再看这一小端。

贪心和动态规划的最明显的区别就是是否使用之前的最优解,如果用到就是动态规划,没有用道具就是贪心。所以我们也可以知道,动态规划是可以解决贪心问题的,但是贪心问题无法解决动态规划问题。


下面我们举出常见的n个类型贪心来进行分析,由于我也是边学习编写博客,所以小伙伴们可能是像看直播一样不断更新的,希望大家在看完各种例题后也能想出自己的分析方法,来判断一个题该如何贪;


一、会场安排问题、节目问题

今年暑假不AC

Description

“今年暑假不AC?”
“是的。”
“那你干什么呢?”
“看世界杯呀,笨蛋!”
“@#$%^&*%…”

确实如此,世界杯来了,球迷的节日也来了,估计很多ACMer也会抛开电脑,奔向电视了。
作为球迷,一定想看尽量多的完整的比赛,当然,作为新时代的好青年,你一定还会看一些其它的节目,比如新闻联播(永远不要忘记关心国家大事)、非常6+7、超级女生,以及王小丫的《开心辞典》等等,假设你已经知道了所有你喜欢看的电视节目的转播时间表,你会合理安排吗?(目标是能看尽量多的完整节目)

Input

输入数据包含多个测试实例,每个测试实例的第一行只有一个整数n(n<=100),表示你喜欢看的节目的总数,然后是n行数据,每行包括两个数据Ti_s,Ti_e (1<=i<=n),分别表示第i个节目的开始和结束时间,为了简化问题,每个时间都用一个正整数表示。n=0表示输入结束,不做处理。

Output

对于每个测试实例,输出能完整看到的电视节目的个数,每个测试实例的输出占一行。

Sample Input

12
1 3
3 4
0 7
3 8
15 19
15 20
10 15
8 18
6 12
5 10
4 14
2 9
0

Sample Output

5

题解:既然想看到更多的节目,我们需要的是尽可能早的看完一个节目从而转战另一个节目,所以我们对节目的结束时间排序,即可求解。
为啥不是优先选择节目时间最短的呢?如果这样的话,我们就少了一个条件,即节目之间不能重叠观看,时间最少的节目之间并不能确定是否时间重叠的数量最少。所以有可能的是由于一个时间较短的节目与几个时间较长的节目重叠,从而舍弃了更多的节目。

CODE:

#include <iostream>#include <algorithm>using namespace std;struct timel {    int strt , endt;} times[105];bool cmp( timel a , timel b ){    return a.endt < b.endt;}int main(){    int n, i, sum, curtime;    while ( scanf( "%d" , &n ) != EOF && n )    {        sum = 0;        for ( i = 0 ; i < n ; i++ )            scanf( "%d %d" , &times[i].strt , &times[i].endt );        sort ( times , times + n , cmp );//排序,从小到大        curtime = 0;  //记录当前节目结束时间          for ( i = 0 ; i < n ; i++ )        {            if ( curtime > times[i].strt )                continue;            else //如果开始时间比上一场节目开始时间晚            {                curtime = times[i].endt;//更新                sum++;            }        }           printf( "%d\n" , sum );         }    return 0;}

会场安排

Description

假设要在足够多的会场里安排一批活动,并希望使用尽可能少的会场。设计一个有效的贪心算法进行安排。(这个问题实际上是著名的图着色问题。若将每一个活动作为图的一个顶点,不相容活动间用边相连。使相邻顶点着有不同颜色的最小着色数,相应于要找的最小会场数。) 对于给定的k个待安排的活动,计算使用最少会场的时间表。

Input

输入数据的第一行有1 个正整数k(k≤10000),表示有k个待安排的活动。接下来的k行中,每行有2个正整数,分别表示k个待安排的活动开始时间和结束时间。时间以0 点开始的分钟计。

Output

输出一个整数,表示最少会场数。

Sample Input

5
1 23
12 28
25 35
27 80
36 50

Sample Output

3

这一个跟上一个题一个道理,每个会场都安排尽可能多的活动,然后每一个活动都遍历一遍,已经确定会场的活动标记一下防止重复选择

CODE:

#include<stdio.h>#include<algorithm>using namespace std;struct s{int a;int b;int flag;};bool cmp (s c,s d){    return c.a<d.a;}int main(){    struct s z[10000]={0},t;    int i,j,sum=0;    int num;    scanf("%d",&num);    for(i=0;i<num;i++)        scanf("%d%d",&z[i].a,&z[i].b);    sort(z,z+num,cmp);    for(i=0;i<num;i++)    {        if(z[i].flag==0)        {            sum++;            t=z[i];        }        for(j=0;j<num;j++)        {            if(z[j].a>=t.b&&z[j].flag==0)            {                z[j].flag=1;                t=z[j];            }        }    }    printf("%d\n",sum);    return 0;}

这种两种事件之间由于某些限制不能共存,需要填满或者经历全部事件的问题,要考虑以限制条件为基础的贪心策略,贪心的方向是使解可以得到进一步优化的方向。
比如第一题,限制条件是事件相冲的两条件不能同时发生,故我们关心的对象应该是开始时间和结束时间,而不是时间长短,那么我们到底对哪方面进行贪心呢,很容易可以得出,只要我可以尽快结束一个节目,我就能看下一个节目了,也就是使我的解更优一点,所以我们对结束时间进行排序。

二、小船过河(捆绑进行)


通过捆绑的方法消除最不利的因素,让最有利的因素更多的发挥。

Crossing River(POJ1700)

Description

A group of N people wishes to go across a river with only one boat, which can at most carry two persons. Therefore some sort of shuttle arrangement must be arranged in order to row the boat back and forth so that all people may cross. Each person has a different rowing speed; the speed of a couple is determined by the speed of the slower one. Your job is to determine a strategy that minimizes the time for these people to get across.
Input

The first line of the input contains a single integer T (1 <= T <= 20), the number of test cases. Then T cases follow. The first line of each case contains N, and the second line contains N integers giving the time for each people to cross the river. Each case is preceded by a blank line. There won’t be more than 1000 people and nobody takes more than 100 seconds to cross.
Output

For each test case, print a line containing the total number of seconds required for all the N people to cross the river.

Sample Input

1
4
1 2 5 10

Sample Output

17
一道英文题,不过翻译很简单,还是简单翻译一下:
只有一艘船,能乘2人,船的运行速度为2人中较慢一人的速度,过去后还需一个人把船划回来,问把n个人运到对岸,最少需要多久。这里需要注意的是,人到了对岸并没走,也就是开船回来的不一定是坐船去的那两个人中的其中一个。


题解:我们考虑如何才能把最不利的因素消除,让最有利的因素更多的发挥,然而此处可知,最不利的肯定是消不了的,因为时间是按最慢的算的,所以我们去消除次最不利的因素,所以我们先把最慢的和次最慢的送到对岸,回来的时候选择最快的方案,那么和这个次最慢的一起坐船的是谁呢?有两个选择,我们来讨论下:
一:这个思路的意思也就是,我让回来的时间最少应该就是时间最短的时间,毕竟去的时候时间怎么也是按慢的时间算的,这样我们很容易就可以想出下一步我们该怎么做,最快的回来再跟最慢的回去就行了。
二:最慢的和他一起,第二次我们可以让最快的和次最快的一起去,,然后让他俩把俩船再开回来这个思路其实也很有道理,既然最慢的怎么也得过去,那我让次最慢的跟他一起岂不少了很多时间,第一种两次过河一次是最慢的一次是次最慢的,第二种方法是一次最慢的一次是此快的大大节省过河时间。
这么一分析貌似第二个更高大上,更像正确答案,但并不是,因为这每个时间的差距是不一定的,第二种回去的时间一次是最快的,可另一次是此最快的,我们写出他们的时间(从小到大排好序后)
一:t[0]+t[n-1]+t[0]+t[n-2]
二:t[0]+t[n-1]+2*t[1]
我们可以看到他们之间的区别是2*t[1]和t[n-2]+t[0];
这两者并不能明确判断哪个大,所以我们每次选择方案时还是需要比较一番的。
下面我们上代码:

CODE:

#include <iostream>#include <algorithm>#include <stdio.h>using namespace std;int main(){    int a[1000],t,n,sum;    scanf("%d",&t);    while(t--)    {        scanf("%d",&n);        sum=0;        for(int i=0;i<n;i++)             scanf("%d",&a[i]);        while(n>3)        {            sum+=min(2*a[1]+a[0]+a[n-1],2*a[0]+a[n-1]+a[n-2]);            n-=2;        }        if(n==3) sum+=a[0]+a[1]+a[2];        else if(n==2) sum+=a[1];        else sum+=a[0];        printf("%d\n",sum);    }}
原创粉丝点击