描述
你刚刚继承了流行的“破锣摇滚”乐队录制的尚未发表的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;
}