贪心总结

贪心选择是指所求问题的整体最优解可以通过一系列局部最优的选择,即贪心选择来达到。这是贪心算法可行的第一个基本要素,也是贪心算法与动态规划算法的主要区别。贪心选择是采用从顶向下、以迭代的方法做出相继选择,每做一次贪心选择就将所求问题简化为一个规模更小的子问题。对于一个具体问题,要确定它是否具有贪心选择的性质,我们必须证明每一步所作的贪心选择最终能得到问题的最优解。通常可以首先证明问题的一个整体最优解,是从贪心选择开始的,而且作了贪心选择后,原问题简化为一个规模更小的类似子问题。然后,用数学归纳法证明,通过每一步贪心选择,最终可得到问题的一个整体最优解。只顾眼前的利益,就可以得到最优解。
贪心 与动态规划 区别
1.贪心选择是采用从顶向下、以迭代的方法做出相继选择,每做一次贪心选择就将所求问题简化为一个规模更小的子问题。贪心算法是动态规划方法的一个特例,可以证明每一个子树的根的值不取决于下面叶子的值,而只取决于当前问题的状况。换句话说,不需要知道一个节点所有子树的情况,就可以求出这个节点的值。由于贪心算法的这个特性,它对解空间树的遍历不需要自底向上,而只需要自根开始,选择最优的路,一直走到底就可以了。
贪心着眼现实当下,动规谨记历史进程。
贪心是求局部最优,以得到全局最优(不一定是正确的,需要证明)。

2.动态规划方法代表了这一类问题的一般解法,我们自底向上构造子问题的解,对每一个子树的根,求出下面每一个叶子的值,并且以其中的最优值作为自身的值,其它的值舍弃。
动态规划是贪心的泛化,贪心 是动态规划 的特例。
dp是通过一些状态来描述一些子问题,然后通过状态之间的转移来求解(一般只要转移方程是正确的,答案必然是正确的)。

活动选择问题
分析::

贪心策略:对输入的活动以其完成时间的非减序排列,算 法每次总是选择具有最早完成时间的相容活动加入最优解 集中。直观上,按这种方法选择相容活动为未安排活动留下尽可能多的时间。也就是说,该算法的贪心选择的意义是使剩余的可安排时间段极大化,以便安排尽可能多的相 容活动。

1.今年暑假不AC
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

代码::
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
const int maxn=100;
typedef struct
{
    int start;
    int end;    
}S;
S stu[maxn];
int cmp(S a,S b)
{
    return a.end<=b.end;
}
int main()
{
    int n,i=0;
    while(cin>>n&&n)
    {
        int k=1;
        memset(stu,0,sizeof(stu));
    
        for(i=0;i<n;i++)
            cin>>stu[i].start>>stu[i].end;
        
        sort(stu,stu+n,cmp);
        i=0;
        for(int j=1;j<n;j++)
            if(stu[j].start>=stu[i].end)
            {
                k++;
                i=j;
            }
            cout<<k<<endl;
    }
    return 0;
}

2. 
Ignatius刚从第三十届ACM/ICPC中学毕业。现在他有很多作业要做。每个老师给他一个交作业的最后期限。如果伊格纳修斯在期限后交作业,老师会降低期末考试的分数。现在我们假设每个人做作业总是要花一天时间。所以伊格纳修斯想让你帮他安排家庭作业的顺序,以便把减少的分数降到最低。

(可能会想错,想到先去排时间,先做时间最紧,可能得不到最优解,应该是先去 做份最高的作业,如果遇到分相同的,先做时间紧张的)


输入
输入包含多个测试用例。输入的第一行是单个整数T,这是测试用例的数目。t试验病例随访。              每个测试用例以正整数n(1<n<=1000)开始,表示作业数量。接着2行。第一行包含N个整数,表示主题的最后期限,下一行包含N个整数,表示分数的降低。


输出
对于每个测试用例,您应该输出最小的总减少分数,每个测试用例一行。

Sample Input
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
 
Sample Output
0
3
5

代码::

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
typedef long long ll;
const int maxn=1000;
typedef struct
{
    int deadline;
    int score;
}S;
S stu[maxn];
int cmp(S a,S b)
{
    if(a.score==b.score)
        return a.deadline<b.deadline;        
    return a.score>b.score;    
}
int main()
{
    int t,n,j,i,sum;
    cin>>t;
    bool a[maxn];
    while(t--)
    {
        memset(a,0,sizeof(a));
        memset(stu,0,sizeof(stu));
        sum=0;
        cin>>n;
        for(i=0;i<n;++i)
            cin>>stu[i].deadline;
        for(i=0;i<n;++i)
            cin>>stu[i].score;                                
        sort(stu,stu+n,cmp);        
        for(i=0;i<n;i++)
            if(!a[stu[i].deadline])
            {
                a[stu[i].deadline]=true;
                continue;
            }
            else
            {
                for(j=stu[i].deadline;j>=1;j--)
                    if(!a[j])
                    {
                        a[j]=true;
                        break;
                    }
                if(j==0)
                    sum+=stu[i].score;                
            }
        cout<<sum<<endl;        
    }                
        return 0;
}

3. 背包相关问题 

1.最优装载问题

分析::
给出 n 个物体,第i 个物体的 重量为wi,。选择尽量多的物体,使得总重量不超过C。
由于只关心物体的的数量,所以装重的没有装轻的划算。只需把所有物体按重量从小到大排序,依次选择每个物体,直到装不下为止。这是一种典型的贪心算法,他只顾眼前,但却依然能得到最优解。

 

 

2.部分背包(物品可拿部分)/小数背包问题 可以用贪心算法。

有n个物体,第i个物体的 重量为 wi,价值为 vi。在总重量 不超过c 的情况下 ,让价值尽量高。每一个物体 都可以只取走一部分,价值和重量按比例 计算。

分析::
本题在上一题的基础上,增加了价值,所以不能简单的像上题那样先拿轻的,(轻的可能价值也小),也不能先拿价值大的(可能他特别重),而应该综合考虑两个因素。一种是直观的 贪心 策略:优先拿“价值处以重量的值”最大的,直到重量和正好为c。
这种问题一定可以装满。
所以按照价值率(价值除以质量)来装物品,使得单位重量价值增长最快。保证了价值和重量,是最优解


例题::
题目描述


有一个背包,背包容量是M(0<M≤500),有N(1<N≤1000)个物品,物品可以分割成任意大小。
  要求尽可能让装入背包中的物品总价值最大,但不能超过总容量。


输入


第1行有两个数,M和N;
第2行到N+I行:第i行为第i-1个物品的价值和质量(均为小于100的正整数),中间用空格隔开。

输出

只有一个数为最大总价值(保留一位小数)。

样例输入
150 7
10 35
40 30
30 60
50 50
35 40
40 10
30 25
样例输出
190.6

代码 ::

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
typedef long long ll;
const int maxn=1010;
typedef struct 
{
    int val;
    int kg;
    double val_kg;
}S;
S wupin[maxn];
bool cmp(S a,S b)
{
    return a.val_kg>b.val_kg;
}
int main()
{
    int maxw,n,i;
    cin>>maxw>>n;
    for(i=1;i<=n;i++)
    {
        cin>>wupin[i].val>>wupin[i].kg;
        wupin[i].val_kg=1.0*wupin[i].val/wupin[i].kg;
    }
    
    sort(wupin+1,wupin+n+1,cmp);
    
    i=1;
    

    double sum=0;
    
    while(maxw>0)
    {
        if(wupin[i].kg<=maxw)
        {
            maxw=maxw-wupin[i].kg;
            sum=sum+wupin[i].val;
        }
        else
        {
            sum=sum+maxw*wupin[i].val_kg;
            break;    
        }
        i++;
        
    }
    printf("%.1lf\n",sum);
    return 0;
}

3.乘船问题
分析:
考虑最轻的人i,他应该和谁一起做呢?如果每个人都无法和他做一条船,则唯一的方法是每人坐一条船,(想一想为什么)。否则,他应该选择能和他一起坐船的人中最重的一个j。这样的方法是贪心,因此他只是让“眼前”浪费最少。幸运的是,这个贪心策略 是正确的,可以用反证法证明。
假设这样做不是最好的,那么最好的解决方案中i 是什么样的呢?

情况1::i不和任何一个人k

情况2::i和另外一个人k同船。由贪心策略,j是“可以和i一起坐船的人”中最重的,因此k比j轻。把j和k交换后k所在的船仍然不会超重(因为k比j轻),而i和j所在的船也不会超重(由贪心过程),因此所得到的新解不会更差。
由此可见,贪心法不会丢失最优解。最后说一下程序实现。在刚才的分析中,比j更重的人只能每人坐一条船。这样,只需用两个下标i和j分别表示当前考虑最轻的人和最重的人,每次先将j往左移动,直到i和j可以坐在一条船上,然后i加1,j减1,并重复上述操作。不难看出,程序的时间复杂度仅为O(n),是最优的算法(别忘了,读入数据也需要O(n)时间,因此无法比这个更好了)。


描述
有n个人,第i个人重量为w[i]。每艘船的最大载重量均为C,且最多只能乘两个人。请问如何用最少的船装载所有人。
输入 

输入有多组数据。每组数据第一行输入两个整数n和C,表示有n个人,每艘船最大载重量为C,第二行输入n个整数w[i],表示每个人的重量。所有数据均在[1, 1000]内,对于所有的w[i],都有w[i] <= C。

输出 

对应每组数据,输出一个整数,表示装载所有人用的最少船数。

样例输入1 
3 100
50
90
40
样例输出1 
2

代码 ::


#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
typedef long long ll;
const int maxn=1010;
int w[maxn];
int main()
{
    ll n,maxw,i;
    while(cin>>n>>maxw)
    {
        for(i=1;i<=n;i++)
            cin>>w[i];
            sort(w,w+n);
        int sum=0,stu=1;
        while(stu<=n)    
        {
            if(w[stu]+w[n]<=maxw)
            {
                stu++,n--;
                sum++;
            }
            else
            {
                n--;
                sum++;
            }
        }
        cout<<sum<<endl;
        
    }
    
    
    return 0;

4.区间相关问题
@1.选择不相交区间
数轴上有n个开区间(ai,bi)。选择尽量多个区间,使得这些区间两两没有公共点。

分析::
首先明确一个问题,假设有两个区间x,y,区间x完全包含y。那么,选x是不划算的,因为x和y最多只能选一个,选x还不如选y,这样不仅区间数目不会减少,而且给其他间留出了更多的位置。接下来,按照bi从小到大的顺序给区间排序。贪心策略是:一定要选择第一个区间。为什么?
现在区间已经排序成b1<=b2<=b3......了,考虑a1和a2的大小关系。

情况1:a1>a2,如图 (/*/*/*有个图/*/*/*/*)所示,区间2包含区间1。前面已经讨论过,这种情况下一 定不会选择区间2。不仅区间2如此,以后所有区间中只要有一个i满足a1>ai,i都不要选。在 今后的讨论中,将不考虑这些区间。

 

情况2:排除了情况1,一定有a1≤a2≤a3≤…,如图(/*/*/*/*有图/*//*/*/*)所示。如果区间2和区间1完全 不相交,那么没有影响(因此一定要选区间1),否则区间1和区间2最多只能选一个。如果 不选区间2,黑色部分其实是没有任何影响的(它不会挡住任何一个区间),区间1的有效部 分其实变成了灰色部分,它被区间2所包含!由刚才的结论,区间2是不能选的。依此类推, 不能因为选任何区间而放弃区间1,因此选择区间1是明智的。

 

 

 

 

选择了区间1以后,需要把所有和区间1相交的区间排除在外,需要记录上一个被选择的 区间编号。这样,在排序后只需要扫描一次即可完成贪心过程,得到正确结果。

例题

描述
学校的小礼堂每天都会有许多活动,有时间这些活动的计划时间会发生冲突,需要选择出一些活动进行举办。小刘的工作就是安排学校小礼堂的活动,每个时间最多安排一个活动。现在小刘有一些活动计划的时间表,他想尽可能的安排更多的活动,请问他该如何安排。


输入第一行是一个整型数m(m<100)表示共有m组测试数据。
每组测试数据的第一行是一个整数n(1<n<10000)表示该测试数据共有n个活动。
随后的n行,每行有两个正整数Bi,Ei(0<=Bi,Ei<10000),分别表示第i个活动的起始与结束时间(Bi<=Ei)
输出对于每一组输入,输出最多能够安排的活动数量。
每组的输出占一行


样例输入

2
2
1 10
10 11
3
1 10
10 11
11 20

 

样例输出

1
2

代码::

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
typedef long long ll;
const ll maxn=10000;
typedef struct 
{
    ll start;
    ll end;
}s;

s huodong[maxn];

bool  cmp(s a,s b)
{
    if(a.end==b.end)
         return a.start<b.start;
    else
         return a.end<b.end;
    return 0;
}
int main()
{
    ll t,num,i,sum;
    cin>>t;
    while(t--)
    {
        sum=1;
        memset(huodong,0,sizeof(huodong));
        cin>>num;
        for(i=0;i<num;i++)
            cin>>huodong[i].start>>huodong[i].end;
        sort(huodong,huodong+num,cmp);
        i=0;
        for(int j=1;j<num;j++)
            if(huodong[j].start>huodong[i].end)
            {
                sum++;
                i=j;
            }
        cout<<sum<<endl;        
    }    
    return 0;
}

@2.区间选点问题
数轴上有n个闭区间[ai, bi]。取尽量少的点,使得每个区间内都至少有 一个点(不同区间内含的点可以是同一个)。

分析::

如果区间i内已经有一个点被取到,则称此区间已经被满足。受上一题的启发,下面先 讨论区间包含的情况。由于小区间被满足时大区间一定也被满足,所以在区间包含的情况 下,大区间不需要考虑。
把所有区间按b从小到大排序(b相同时a从大到小排序),则如果出现区间包含的情 况,小区间一定排在前面。第一个区间应该取哪一个点呢?此处的贪心策略是:取最后一个 点,如图(/*/*/*图/*/*/*/)所示。

 

 

例题:

描述

上数学课时,老师给了LYH一些闭区间,让他取尽量少的点,使得每个闭区间内至少有一个点。但是这几天LYH太忙了,你们帮帮他吗?


输入多组测试数据。
每组数据先输入一个N,表示有N个闭区间(N≤100)。
接下来N行,每行输入两个数a,b(0≤a≤b≤100),表示区间的两个端点。
输出输出一个整数,表示最少需要找几个点。


样例输入

4
1 5
2 4
1 4
2 3
3
1 2
3 4
5 6
1
2 2


样例输出

1
3
1

代码::


#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
typedef long long ll;
const ll maxn=10000;
typedef struct 
{
    ll start;
    ll end;
}s;

s qujian[maxn];

bool  cmp(s a,s b)
{
    if(a.end<b.end)
        return 1;
    else if(a.end==b.end)
        return a.start<b.start;
    return 0;

}
int main()
{
    ll t,i,sum;
    while(cin>>t)
    {
        sum=1;
        memset(qujian,0,sizeof(qujian));
        for(i=0;i<t;i++)
            cin>>qujian[i].start>>qujian[i].end;
        sort(qujian,qujian+t,cmp);
        i=0;
        for(int j=1;j<t;j++)
            if(qujian[j].start>qujian[i].end)
            {
                sum++;
                i=j;
            }            
        cout<<sum<<endl;        
    }
    return 0;
}

例题:
TIANKENG’s restaurant
(与活动安排不太一样)
天津在ZCMU毕业后经营着一家餐馆,数以万计的顾客前来就餐,因为它的菜肴很好吃。如今,N组的顾客开始享用他们的饭菜,在第三组中有十一个人。假设每个顾客只能拥有一把椅子。现在我们知道各组的到达时间STI和离开时间EDi。你能帮天行计算一下他需要准备的最小椅子吗?这样每位顾客到饭店时都能坐下吗?     
       
输入           
 
第一行包含正整数T(t<100),代表t检验病例。      
每个案例都有正整数n(1<n=10000),这意味着n组客户。然后在n行之后,每一行都是正整数Xi(1<Xi<100),参照第i组人的数目和到达时间STI和出发时间Edi(时间格式是HH:mm,0<HH<24, 0<24, 0 mm=60),因为到达时间必须早于离开T。IME。  请注意,当一群人一离开餐厅就到达餐厅时,如果座位足够,那么到达的团队可以安排就座。      

输出        
对于每一个测试用例,输出天坑需要准备的椅子的最小数量。 


Sample Input
2
2
6 08:00 09:00
5 08:59 09:59
2
6 08:00 09:00
5 09:00 10:00
 

Sample Output
11
6


代码::


#include<cstdio>
#include<cstring>
#include<iostream> 
#include<algorithm>
using namespace std;
int a[1500];
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        memset(a,0,sizeof(a));
        int n;
        scanf("%d",&n);
        int num,st1,st2,ed1,ed2,st,ed;
        for(int i=1 ; i<=n ; i++)
        {
            scanf("%d %d:%d %d:%d",&num,&st1,&st2,&ed1,&ed2);
            st=st1*60+st2;
            ed=ed1*60+ed2;            
            for(int j=st;j<ed;j++)     
                a[j]+=num;
        }
        sort(a,a+1500);
        cout<<a[1499]<<endl;
    }
    return 0;    

@3.区间覆盖问题

数轴上有n个闭区间[ai, bi],选择尽量少的区间覆盖一条指定线段[s,t]。
【分析】
本题的突破口仍然是区间包含和排序扫描,不过先要进行一次预处理。每个区间在[s, t] 外的部分都应该预先被切掉,因为它们的存在是毫无意义的。预处理后,在相互包含的情况 下,小区间显然不应该考虑。
把各区间按照a从小到大排序。如果区间1的起点不是s,无解(因为其他区间的起点更 大,不可能覆盖到s点),否则选择起点在s的最长区间。选择此区间[ai, bi] 后,新的起点应 该设置为bi,并且忽略所有区间在bi之前的部分,就像预处理一样。虽然贪心策略比上题复 杂,但是仍然只需要一次扫描,如图8-9所示。s为当前有效起点(此前部分已被覆盖),则 应该选择区间2。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值