天天看點

貪心/貪婪算法

貪心算法簡介

簡單貪心

貪心法是求解一類最優化問題的方法,它總是考慮在目前狀态下局部最優(較優)的政策,來使全局的結果達到最優(較優),顯然如果采用較優而非最優的政策,得到的全局結果也不一定是最優的。而要獲得最優結果,必須保證中間的每步政策都是最優的,是以嚴謹使用貪心政策來求解最優化問題需要對政策進行證明。一般來說,證明比較繁瑣情況下,如果在想到某個似乎可行的政策,并且自己也無法舉出反例,可以直接大膽使用。

執行個體一 月餅

給定所有種類月餅的庫存量,總售價以及市場的最大需求量,試計算可以獲得的最大收益。

輸入格式:正整數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;
}
           

動态規劃解法:

程式員算法基礎——貪心算法 - 簡書

繼續閱讀