[算法总结]贪心


贪心算法是每次选举决策时保证当前状况最优(局部最优)策略的算法。因此在使用贪心时需要保证整体最优解可以由局部最优解导出。
贪心算法的使用通常需要证明,以下为几种常见的证明方法:

1. 邻项交换:

在任意局面下,任何元素位置的改变都会影响当前局面的改变。该方法NOIP曾有涉及。
对局面元素的排序可以为贪心策略提供证明。

2. 范围缩放:

扩大局部最优解的范围不会影响整体最优的情况。

3. 反证法,归纳法:

如同字面意思。 (=´ω`=)

例题:

1.P2887 [USACO07NOV]防晒霜Sunscreen

分析:
把每个奶牛按照minSPF递减排序,并在防晒霜中找SPF最大的给这头奶牛用(在合理范围内)。假如有两瓶防晒霜x,y可用,其中spf[x]>spf[y]。那么对于下一头奶牛来说,有x,y都能用,只能用y,和x,y都不能用这三种情况。显然,我们将spf低的防晒霜给后面的奶牛用会更优。
代码如下:

#include<bits/stdc++.h>
#define N 5000
using namespace std;
struct node{
    int l,r;
    bool operator <(const node &other)const{
        return l>other.l;
    }
}c[N];
struct temp{
    int num,power;
    bool operator <(const temp &other)const{
        return power>other.power;
    }
}s[N];
int n,m,ans;
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d%d",&c[i].l,&c[i].r);
    for(int i=1;i<=m;i++)
        scanf("%d%d",&s[i].power,&s[i].num);
    sort(c+1,c+n+1);
    sort(s+1,s+1+m);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++){
            if(s[j].power>=c[i].l&&s[j].power<=c[i].r&&s[j].num){
                s[j].num--;ans+=1;break;
            }
        }
    printf("%d",ans);
    return 0;
}

2.P2859 [USACO06FEB]摊位预订Stall Reservations

分析:
按照奶牛们开始挤奶的的时间排序,如果与下一个牛的挤奶时间与上一头牛的时间重合,那么新建一个牛棚。
代码如下:

#include<bits/stdc++.h>
#define N 50010
using namespace std;
int n,num=1;
struct node{
    int l,r,id,vis;
    bool operator <(const node &other)const{
        return l<other.l;
    }
}c[N];
inline int cmp(node x,node y){
    return x.id<y.id;
}
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > > q;
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d%d",&c[i].l,&c[i].r);
        c[i].id=i;
    }
    sort(c+1,c+n+1);
    q.push(make_pair(c[1].r,1));
    c[1].vis=1;
    for(int i=2;i<=n;i++){
        int stop=q.top().first;
        int snum=q.top().second;
        if(c[i].l>stop){
            q.pop();q.push(make_pair(c[i].r,snum));
            c[i].vis=snum;
        }
        else{
            num++;
            c[i].vis=num;
            q.push(make_pair(c[i].r,num));
        }
    }
    printf("%d\n",num);
    sort(c+1,c+1+n,cmp);
    for(int i=1;i<=n;i++)
        printf("%d\n",c[i].vis);
    return 0;
}

3.P1080 国王游戏

分析:
本题的解题关键就在于,如何排序使得得到金币最多的大臣获得的金币数最少。
我们可以使用邻项交换的方式推导,设两个相邻的大臣\(x\)\(x+1\),我们分别计算这两个大臣在交换位置之前和交换位置之后的最大值。得到下列柿子:
\[max\left(\frac{1}{B[i]},\frac{A[i]}{B[i+1]}\right)\] \[max\left(\frac{1}{B[i+1]},\frac{A[i+1]}{B[i]}\right)\]
此时两边同时乘\(B[i]*B[i+1]\),可得:
\[max\left(B[i+1],A[i]*B[i]\right)\]
\[max(B[i],A[i+1]*B[i+1])\]
比较可得,当\(A[i]*B[i]\leq A[i+1]*B[i+1]\)时,序列更优。
代码如下:(高精度,不写只有60分)

#include<bits/stdc++.h>
#pragma GCC O(2)
#define N 10100
#define INF 0x7f7f7f7f
#define ll long long
using namespace std;
int n,L,R;
int ans[N<<1],sum[N<<1],add[N<<1];
struct node{
    int l,r;
    ll key;
    bool friend operator < (node x,node y){
        return x.key<y.key;
    }
}p[N];
inline void modify(){
    for(int i=add[0];i>=0;i--)
        sum[i]=add[i];
}
inline int compare(){
    if(sum[0]>add[0]) return 0;
    if(sum[0]<add[0]) return 1;
    for(int i=add[0];i>=1;i--){
        if(add[i]>sum[i]) return 1;
        if(add[i]<sum[i]) return 0;
    }
}
inline void multiply(int num){
    memset(add,0,sizeof(add));
    for(int i=1;i<=ans[0];i++){
        ans[i]=ans[i]*num;
        add[i+1]+=ans[i]/10;
        ans[i]%=10;
    }
    for(int i=1;i<=ans[0]+5;i++){
        ans[i]+=add[i];
        if(ans[i]>=10){
            add[i+1]+=ans[i]/10;
            ans[i]%=10;
        }
        if(ans[i]) ans[0]=max(ans[0],i);
    }
}
inline void divid(int num){
    memset(add,0,sizeof(add));
    int ext=0;
    for(int i=ans[0];i>=1;i--){
        ext*=10;
        ext+=ans[i];
        add[i]=ext/num;
        if(add[0]==0&&add[i]) add[0]=i;
        ext%=num;
    }
}
int main()
{
    scanf("%d",&n);
    for(int i=0;i<=n;i++){
        scanf("%d%d",&p[i].l,&p[i].r);
        p[i].key=p[i].l*p[i].r;
    }
    sort(p+1,p+n+1);
    ans[0]=ans[1]=1;
    for(int i=1;i<=n;i++){
        multiply(p[i-1].l);
        divid(p[i].r);
        if(compare()) modify();
    }
    for(int i=sum[0];i>=1;i--)
        printf("%d",sum[i]);
    return 0;
}

4.P1417 烹调方案

分析:
这到题和上一道题本质是一样的,都是用邻项交换作证明的,只不过这道题的推导简单一些。本题若除去b数组,那么就属于经典的线性Dp问题。想要解决元素之间的先后顺序,不妨设两个食材为\(x,y\),因此代入公式\((ai-t×bi)\)可得:
其中\(p\)为先前做完食材的时间。
\[a[x]-(p+c[x])*b[x]+a[y]-(p+c[x]+c[y])*b[y]\]
\[a[y]-(p+c[y])*b[y]+a[x]-(p+c[y]+c[x])*b[x]\]
化简后易得:\[c[x]*b[y]<c[y]*b[x]\]
因此只要按照这个优先级对食材排序,再进行排序就可以保证答案的正确性。
代码如下:

#include<bits/stdc++.h>
#define N 100010
#define int long long
using namespace std;
struct node{
    int a,b,c;
}p[N];
inline int cmp(node x,node y){
    return x.c*y.b<y.c*x.b;
}
int f[N],t,n;
signed main()
{
    scanf("%d%d",&t,&n);
    for(int i=1;i<=n;i++) scanf("%d",&p[i].a);
    for(int i=1;i<=n;i++) scanf("%d",&p[i].b);
    for(int i=1;i<=n;i++) scanf("%d",&p[i].c);
    sort(p+1,p+n+1,cmp);
    for(int i=1;i<=n;i++)
        for(int j=t;j>=p[i].c;j--){
            f[j]=max(f[j],f[j-p[i].c]+p[i].a-j*p[i].b);
        }
    int ans=0;
    for(int i=1;i<=t;i++) ans=max(ans,f[i]);
    printf("%d",ans);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/cyanigence-oi/p/11756470.html