USACO-Section 3.4 Raucous Rockers (DP)

58 篇文章 0 订阅

描述

你刚刚继承了流行的“破锣摇滚”乐队录制的尚未发表的N(1 <= N <= 20)首歌的版权。你打算从中精选一些歌曲,发行M(1 <= M <= 20)张CD。每一张CD最多可以容纳T(1 <= T <= 20)分钟的音乐,一首歌不能分装在两张CD中。

不巧你是一位古典音乐迷,不懂如何判定这些歌的艺术价值。于是你决定根据以下标准进行选择:

1.歌曲必须按照创作的时间顺序在所有的CD盘上出现。(注:第i张盘的最后一首的创作时间要早于第i+1张盘的第一首)

2.选中的歌曲数目尽可能地多。

格式

PROGRAM NAME: rockers

INPUT FORMAT:

(file rockers.in)

第一行: 三个整数:N, T, M.

第二行: N个整数,分别表示每首歌的长度,按创作时间顺序排列。

OUTPUT FORMAT:

(file rockers.out)

一个整数,表示可以装进M张CD盘的乐曲的最大数目。

SAMPLE INPUT

4 5 2
4 3 4 2

SAMPLE OUTPUT

3

解法一:DP【O(n*t*m)】

又是知道应该用DP做,但是不知道如何转移

思索好久,想出来一个特别麻烦的转移方程

设dp[i][j]表示用到第i张唱片的前j分钟时(第i张唱片没有任何时间浪费)能保存最多的歌曲数,初始dp[1][0]=0(表示有效状态),其余为-1

从后开始枚举唱片,其中再从后开始枚举用过的时间,当当前状态有效时进行转移(我为人人型)

进行到dp[i][j]时,若能存下这首歌,则在第i张唱片存这首歌,否则第i+1张唱片存这首歌(前提是时长不超过限制)

/*
ID: your_id_here
PROG: rockers
LANG: C++
*/
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

int n,t,m,minu,dp[25][25],ans;//dp[i][j]表示用到第i张唱片的前j分钟时能保存最多的歌曲数

int main() {
    freopen("rockers.in","r",stdin);
    freopen("rockers.out","w",stdout);

    scanf("%d%d%d",&n,&t,&m);
    memset(dp,-1,sizeof(dp));
    ans=dp[1][0]=0;
    while(n-->0) {
        scanf("%d",&minu);
        for(int i=m;i>=1;--i) {//从后枚举唱片,类似01背包
            for(int j=t;j>=0;--j) {//从后枚举第i张唱片用的时间,类似01背包
                if(dp[i][j]!=-1) {//如果这个状态有效
                    if(j+minu<=t) {//如果放入这首歌不超过时长限制
                        if(dp[i][j]+1>dp[i][j+minu]) {//转移
                            dp[i][j+minu]=dp[i][j]+1;
                            ans=max(ans,dp[i][j+minu]);//更新
                        }
                    }
                    else {
                        if(i+1<=m&&minu<=t&&dp[i][j]+1>dp[i+1][minu]) {//如果不超过唱片数且不超过时长限制,转移
                            dp[i+1][minu]=dp[i][j]+1;
                            ans=max(ans,dp[i+1][minu]);//更新
                        }
                    }
                }
            }
        }
    }
    printf("%d\n",ans);
    return 0;
}

又想了一下,其实dp全部初始化为0即可,可以证明,除了dp[1][0]以外转移所得的结果不会比dp[1][0]转移而来的结果更优

翻了一下《背包九讲》,了解到①如果要求背包全部装满,则只有初始的一个合法状态,其余为-1②如果要求背包所装物品不超过背包容量,即可令dp全部为0


优化后:

再写了一遍简洁的

设dp[i][j]表示用到第i张唱片的前j分钟时(允许第i张唱片中的歌曲伟占满前j分钟)能保存最多的歌曲数,初始dp全部为0

从后开始枚举唱片,其中再从后开始枚举用过的时间(人人为我型)

dp[i][j]可由两种状态转移而来:①第i张唱片前j-minu分钟的最大存歌数:dp[i][j-minu],②第i-1张唱片的最大存歌数:dp[i][t]

/*
ID: your_id_here
PROG: rockers
LANG: C++
*/
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

int n,t,m,minu,dp[25][25];//dp[i][j]表示用到第i张唱片的前j分钟时能保存最多的歌曲数

int main() {
    freopen("rockers.in","r",stdin);
    freopen("rockers.out","w",stdout);

    scanf("%d%d%d",&n,&t,&m);
    memset(dp,0,sizeof(dp));
    while(n-->0) {
        scanf("%d",&minu);
        for(int i=m;i>=1;--i) {//从后枚举唱片,类似01背包
            for(int j=t;j>=minu;--j) {//从后枚举第i张唱片用的时间,类似01背包
                dp[i][j]=max(dp[i][j],max(dp[i-1][t],dp[i][j-minu])+1);
            }
        }
    }
    printf("%d\n",dp[m][t]);
    return 0;
}


解法二:DP【O(n^2)】

又看到一种O(n^2)的dp,写了一下,感觉比较麻烦

dp[i][j]是一个结构体,表示前i首歌取j首歌时用的最少的唱片数,a表示用了a张唱片,b表示第a张唱片用了b分钟

状态转移方程:dp[i][j]=min(dp[i-1][j],dp[i-1][j-1]⊕1)

此处dp[i-1][j-1]⊕1表示:①minu+dp[i-1][j-1].b>t时:dp[i-1][j-1].a+1,dp[i-1][j-1].b不变;②minu+dp[i-1][j-1]<=t时:dp[i-1][j-1].b+minu,dp[i-1][j-1].a不变

/*
ID: your_id_here
PROG: rockers
LANG: C++
*/
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

int n,t,m,minu,ans;

struct Node {
    int a,b;

    bool operator<(const Node& x) const {
        return a<x.a||(a==x.a&&b<x.b);
    }

    bool operator<=(const Node& x) const {
        return a<x.a||(a==x.a&&b<=x.b);
    }
}dp[25][25],tmp;//dp[i][j]表示用到前i首歌选取j首歌所占的最小唱片数,a表示用了a张唱片,b表示第a张唱片用了b分钟

int main() {
    freopen("rockers.in","r",stdin);
    freopen("rockers.out","w",stdout);

    scanf("%d%d%d",&n,&t,&m);
    memset(dp,0x3f,sizeof(dp));
    for(int i=0;i<=n;++i) {
        dp[i][0].a=1;
        dp[i][0].b=0;
    }
    for(int i=1;i<=n;++i) {
        scanf("%d",&minu);
        if(minu<=t) {
            for(int j=1;j<=i;++j) {
                if(minu+dp[i-1][j-1].b>t) {
                    dp[i][j].a=dp[i-1][j-1].a+1;
                    dp[i][j].b=minu;
                }
                else {
                    dp[i][j].a=dp[i-1][j-1].a;
                    dp[i][j].b=minu+dp[i-1][j-1].b;
                }//dp[i][j]由dp[i-1][j-1]转移而来
                dp[i][j]=min(dp[i-1][j],dp[i][j]);//dp[i][j]由dp[i-1][j]转移而来
            }
        }
    }
    ans=0;
    tmp.a=m;
    tmp.b=t;
    for(int j=n;j>=1;--j) {
        if(dp[n][j]<=tmp) {
            ans=j;
            break;
        }
    }
    printf("%d\n",ans);
    return 0;
}


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值