贪心/贪婪算法

贪心算法简介

简单贪心

贪心法是求解一类最优化问题的方法,它总是考虑在当前状态下局部最优(较优)的策略,来使全局的结果达到最优(较优),显然如果采用较优而非最优的策略,得到的全局结果也不一定是最优的。而要获得最优结果,必须保证中间的每步策略都是最优的,因此严谨使用贪心策略来求解最优化问题需要对策略进行证明。一般来说,证明比较繁琐情况下,如果在想到某个似乎可行的策略,并且自己也无法举出反例,可以直接大胆使用。

实例一 月饼

给定所有种类月饼的库存量,总售价以及市场的最大需求量,试计算可以获得的最大收益。

输入格式:正整数N表示月饼种类数(N<=1000),正整数D表示最大需求量(D<=500万吨);随后一行给出N个正整数表示每种月饼的库存,最后一行给出N个正整数表示每种月饼的总售价。

输入:在一行中输出最大收益,以亿元为单位并精确到小数点后2位。

输入:
3 20
18 15 10
75 72 45
输出:
94.5

容易看出,答案是72+45/2=94.5。原则就是总是采用单价最高的月饼出售。

因此,思路如下:

(1)将所有月饼按照单价从高到低排序。

(2)从单价高的月饼开始枚举。如果该月饼的库存不足以填补需求,将该种月饼全部售出。需求量减少该种月饼的库存大小,收益增加该月饼的总售价。如果月饼库存足够填补需求,则只提供需求量大小的月饼。需求量减为0,收益增加。

#include <cstdio>
#include <algorithm>

using namespace std;

struct mooncake{
    double store;  //库存
    double sell;   //总售价
    double price;  //单价
}cake[1010];

//按照单价由高向低排序
bool cmp(mooncake a,mooncake b)
{
    return a.price>b.price;
}

int main()
{
    int n;
    double D;
    scanf("%d%lf",&n,&D);
    for(int i=0;i<n;i++)
    {
        scanf("%lf",&cake[i].store);
    }

    for(int i=0;i<n;i++)
    {
        scanf("%lf",&cake[i].sell);
        cake[i].price=cake[i].sell/cake[i].store;
    }
    sort(cake,cake+n,cmp);
    double ans=0;  //收益
    for(int i=0;i<n;i++)
    {
        if(cake[i].store<=D)
        {
            D-=cake[i].store;
            ans+=cake[i].sell;
        }
        else
        {
            ans+=cake[i].price*D;
            break;
        }
    }
    printf("%.2f\n",ans);
    return 0;
}

实例二:组个最小数

给定数字0-9若干个,可以任意顺序排列这些数字,但必须全部使用。目标是使得最后得到的数尽可能小(0不可以为首位)。例如给定2个0,2个1,3个5,1个8,得到的最小数就是10015558。

输入格式:在一行中给出10个非负整数,顺序表示拥有数字0,1,......8,9的个数。十个数字的总个数不超过50,而且至少拥有1个非0数字。

输入
2 2 0 0 0 3 0 0 1 0
输出
10015558

策略:先从1-9中选择个数不为0的最小数输出,然后从0-9输出数字,每个数字输出次数就是其剩余次数。

#include <cstdio>

int main()
{
    int count_num[10];
    for(int i=0;i<10;i++)
    {
        scanf("%d",&count_num[i]);
    }

    for(int i=1;i<10;i++)
    {
        if(count_num[i]>0)
        {
            printf("%d",i);
            count_num[i]--;
            break;
        }
    }

    for(int i=0;i<10;i++)
    {
        while(count_num[i]>0)
        {
            printf("%d",i);
            count_num[i]--;
        }
    }
    return 0;
}

区间贪心

区间不相交问题:给出N个开区间(x,y),从中选择尽可能多的开区间,使得这些开区间22没有交集。例如对于开区间

(1,3)(2,4) (3,5) (6,7)

最多可以选择3个开区间

(1,3) (3,5) (6,7)

互相没有交集。

首先考虑最简单的情况,如果开区间A1被开区间A2包含,那么显然选择开区间A1是最好的选择。

接着把所有开区间按照左端点x从大到小排序,如果去除掉区间包含的情况,必有y1>y2>y3>.....>yn成立。那么如何选择区间。如图所示:A1的右边有一段是一定不会和其他区间重叠的,如果把他去掉,那么A1就会被包含,这时应该选择A1.因此对于这种情况应该总是先选择

左端点最大的区间。

#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=110;

struct Inteval
{
    int x,y;
}I[maxn];

bool cmp(Inteval a,Inteval b)
{
    if(a.x!=b.x)
        return a.x>b.x;   //先按照左端点从大到小排序
    else
        return a.y<b.y;    //左端点相同的按照右端点从小到大排序
}

int main()
{
    int n;
    scanf("%d",&n);
    for(int i=0;i<n;i++)
    {
        scanf("%d%d",&I[i].x,&I[i].y);
    }
    sort(I,I+n,cmp);
    int ans=1;
    int lastX=I[0].x;
    for(int i=1;i<n;i++)
    {
        if(I[i].y<=lastX)
        {
            lastX=I[i].x;
            ans++;
        }

    }
    printf("%d\n",ans);
    return 0;

}

类似问题:给出N个闭区间【x,y】,求最少需要确定多少点,才可以使得每个区间都至少有一个点。

类似,把区间按照左端点由大到小排序,如果A1被A2包含,则在A1区间内的点一定也在A2区间。我们只需要总是选择最左边端点即可,尽可能覆盖多的区间。

只需修改代码如下即可

I[i].y<=lastX;
I[i].y<lastX;

显然贪心算法用来解决一类最优化问题,并希望由局部最优策略来推得全局最优策略。贪心算法适用的问题一定满足最优子结构的问题。

变种

求区间的交集:合并有交集的区间[1,4,[2,6,[6,7    合并结束是[1,7

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;
const int maxn=105;

struct Interval
{
    int x,y;
}I[maxn];

bool cmp(Interval I1,Interval I2)
{
    if(I1.x!=I2.x)
        return I1.x<I2.x;
    else
        return I1.y>I2.y;
}

int main()
{
    int n;
    cin>>n;
    for(int i=0;i<n;i++)
    {
        cin>>I[i].x;
        cin>>I[i].y;
    }
    sort(I,I+n,cmp);
    vector<Interval> res;

    Interval temp=I[0];
    for(int i=1;i<n;i++)
    {
        if(I[i].y<=temp.y)
            continue;
        else if(I[i].x<=temp.y)
            temp.y=I[i].y;
        else if(I[i].x>temp.y)
        {
            res.push_back(temp);
            temp=I[i];
        }
    }
    res.push_back(temp);
    for(int i=0;i<res.size();i++)
    {
        cout<<res[i].x<<" "<<res[i].y<<endl;
    }
}

实例三

这是一道典型的贪心算法,为了避免多吃的危险,我们在最初先不要消耗一份一盒包装的雪糕,而应该总是选择消耗一份一盒包装的蛋糕最小的方法。即按照下面的方法进行

消耗2盒3包装的蛋糕/3盒2包装的蛋糕     这时消耗1盒包装的最少,为0

消耗1盒3包装的蛋糕,再消耗1盒2包装的蛋糕    此时消耗1盒包装数为1

消耗2盒2包装的蛋糕           此时消耗1盒蛋糕数目为2

消耗1盒3包装的蛋糕           此时消耗1盒蛋糕数目为3

消耗1盒2包装的蛋糕            此时消耗1盒蛋糕数目为4
                        
                            此时消耗1盒蛋糕数目为6



#include <cstdio>
#include <iostream>

using namespace std;
int t;

bool pan_duan(int n,int a,int b,int c)
{
    int day=0;
    if(c>=2)
    {
        day+=c/2;
        c%=2;
    }
    if(b>=3)
    {
        day+=b/3;
        b%=3;
    }
    while(c>=1&&b>=1&&a>=1)
    {
        day+=1;
        c-=1;
        b-=1;
        a-=1;
    }
    while(b>=2&&a>=2)
    {
        day+=1;
        b-=2;
        a-=2;
    }
    while(c>=1&&a>=3)
    {
        day+=1;
        c-=1;
        a-=3;
    }
    while(b>=1&&a>=4)
    {
        day+=1;
        b-=1;
        a-=4;
    }
    day+=a/6;
    if(day>=n)
        return true;
    else
        return false;
}

int main()
{
    scanf("%d",&t);
    int n,a,b,c;
    for(int i=0;i<t;i++)
    {
        scanf("%d%d%d%d",&n,&a,&b,&c);
        if(pan_duan(n,a,b,c))
            printf("Yes\n");
        else
            printf("No\n");
    }
    return 0;
}

博客分享

小船过河问题

只有一艘船,能乘2人,船的运行速度为2人中较慢一人的速度,过去后还需一个人把船划回来,问把n个人运到对岸,最少需要多久。

(1)当只有2个人时,过河时间是t2(t1<t2)

(2)当只有3个人时,过河时间是t1+t2+t3,即最快的把另一个送过河,再返回来送另一个。

(3)当人数多时,>=4时,可以考虑每次都把最慢和次慢的送走。这个时候又有2种方式。

1)最快的把次快的送过去,自己回来,然后最慢的和次慢的过河,已经在对岸的次快把船划回来。时间为a[0]+2a[1]+a[n-1]

2)最快的把最慢送过去,自己回来,然后把次慢(这个时候已经是最慢了)送过河,自己再回来。时间为2a[0]+a[n-2]+a[n-1]

#include <cstdio>
#include <iostream>
#include <algorithm>

using namespace std;

int main()
{
    int n;
    cin>>n;
    int a[n];
    for(int i=0;i<n;i++)
    {
        cin>>a[i];
    }

    sort(a,a+n);

    int sum=0;
    while(n>3)
    {
        sum+=min(a[0]+2*a[1]+a[n-1],2*a[0]+a[n-2]+a[n-1]);
        n-=2;
    }
    if(n==3)
        sum+=a[0]+a[1]+a[2];
    else if(n==2)
        sum+=a[1];
    else
        sum+=a[0];
    cout<<sum<<endl;
    return 0;
}

动态规划解法:
程序员算法基础——贪心算法 - 简书

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值