题目描述
有一首非常流行的歌曲叫《金戈铁马》。它是
37
37
37 首歌曲的混合体,而且非常长(
11
11
11 分
18
18
18 秒)。
为什么它很受欢迎?假设你只剩下
15
15
15 秒,那么你应该尽快选择另一首歌曲,因为 KTV 不会在歌曲结束前粗暴地停止它。如果你选择了一首
2
2
2 分钟的歌曲,你实际上可以得到
105
105
105 个额外的秒 …如果你选择金戈铁马,你会得到
663
663
663 秒的额外时间!!
现在你还有一些时间,但你想现在做一个计划。你应该遵守以下规则:
- 一首歌最多唱 1 1 1 次
- 一首歌的时长为 t t t,要么正好花费时长 t t t 唱它,要么不唱。
- 当一首歌唱完后,立即开始唱新的歌曲
你的目标很简单:唱尽可能多的歌,并尽可能晚地离开 KTV。
输入格式
第一行包含测试案例的数量
T
(
T
≤
100
)
T (T\leq100)
T(T≤100)。
每个测试用例第一行为两个正整数
n
,
t
(
1
≤
n
≤
50
,
1
≤
t
≤
1
0
9
)
n, t (1 \leq n\leq 50, 1\leq t \leq 10^9)
n,t(1≤n≤50,1≤t≤109), 表示候选歌曲的数量(不包括金戈铁马) 和剩余时间(以秒为单位)。
下一行包含
n
n
n 个正整数,即每首歌曲的长度,单位为 秒。每个长度都将小于
3
3
3 分钟
可以保证所有歌曲(包括《金戈铁马》)的长度之和将大于
t
t
t。
输出格式
对于每个测试案例,打印最大的歌曲数量(包括金戈铁马),以及你要唱的歌曲的总长度
样例输入 #1
2
3 100
60 70 80
3 100
30 69 70
样例输出 #1
Case 1: 2 758
Case 2: 3 777
提示
解释一下:
在第一个例子中,我们能做的最好的事情是先唱第三首歌(
80
80
80 秒),然后再唱金戈铁马,再唱
678
678
678 秒。
在第二个例子中,我们先唱前两首(
30
+
69
=
99
30+69=99
30+69=99 秒)。然后我们仍有一秒钟,所以我们可以多唱
678
678
678 秒的《金戈铁马》。但是,如果我们唱第一和第三首歌曲(
30
+
70
=
100
30+70=100
30+70=100 秒),时间已经到了(因为我们总共只有
100
100
100 秒),所以我们不能再唱《金戈铁马》了!
提交链接
https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=25
解析
每个歌曲最多唱一次,01背包。
唱尽可能多的歌,并尽可能晚地离开 KTV。对于
t
t
t 秒的时间,最优的是用
t
−
1
t-1
t−1 秒去唱歌,留下
1
1
1 秒去唱金戈铁马。
状态转移要解决两个问题,一个是唱的歌曲数量,一个是唱的总时间。
对于第 1 1 1 个问题, d p [ i ] dp[i] dp[i] 定义为在 i i i 秒的时间内,初始化 d p [ i ] dp[i] dp[i] 为 0 0 0,所唱的歌曲数量。 d p [ 0 ] ∼ d p [ t − 1 ] dp[0] \sim dp[t-1] dp[0]∼dp[t−1] 中的最大值就是第 1 1 1 个问题的答案。
这样定义状态,第
2
2
2 个问题就无法得到解决。没有办法通过
d
p
dp
dp 数组得到唱歌的时间。
举例:
2首歌曲 10秒的时间
第1首歌曲:3s
第2首歌曲:5s
dp[0]~dp[2]=0;dp[3]~dp[7]=1;dp[8]~dp[9]=2
唱的歌曲数量能正确求得,唱的时间无法求得,因为此时
d
p
[
8
]
dp[8]
dp[8] 和
d
p
[
9
]
dp[9]
dp[9] 都为
2
2
2。
我们可以知道,唱这两首歌的时间为
8
8
8 ,如何让
d
p
[
8
]
dp[8]
dp[8] 的下标
8
8
8 正确表示第
2
2
2 个问题的答案?
d
p
[
i
]
dp[i]
dp[i] :定义为
i
i
i 秒全部用到,唱的歌曲的最大数目,若
i
i
i 秒没有被完全利用,则
d
p
[
i
]
dp[i]
dp[i] 为
−
1
-1
−1。初始化
d
p
[
0
]
dp[0]
dp[0] 为
0
0
0 ,其余为
−
1
-1
−1。
2首歌曲 10秒的时间
第1首歌曲:3s
第2首歌曲:5s
dp[0]=0 dp[1]~dp[2]=-1;dp[3]=1,dp[4]=-1;dp[5]=1,dp[6]~dp[7]=-1;dp[8]=2;dp[9]=-1
此时遍历 for(int i=t-1;i>=0;i--)
,第一个
d
p
[
i
]
dp[i]
dp[i] 等于唱的歌曲数量最大值,此时的
i
i
i 就是我们的唱歌时间。
参考代码
#include<bits/stdc++.h>
using namespace std;
int T,n,t;
int dp[10009]; //dp[i]:i秒恰好用完,唱的歌曲最大数量 无法用完dp[i]=-1
int song[55];
int main()
{
cin>>T; //t组样例
for(int k=1; k<=T; k++)
{
memset(dp,-1,sizeof(dp));
cin>>n>>t; //歌曲的数量 剩余的时间
for(int i=1; i<=n; i++)
cin>>song[i]; //第i首歌曲的时间
dp[0]=0; //!!!
int mx=0; //前t-1秒唱的歌曲的最大数量
for(int i=1; i<=n; i++)
{
for(int j=t-1; j>=song[i]; j--)
{
dp[j]=max(dp[j],dp[j-song[i]]+1);
mx=max(mx,dp[j]);
}
}
cout<<"Case "<<k<<": ";
for(int i=t-1;i>=0;i--)
{
if(dp[i]==mx)
{
cout<<mx+1<<" "<<i+678<<endl; //在t-1秒的规定时间内,实际唱歌i秒
break; //找第一个dp[i]=mx,保证唱歌时间i最大
}
}
}
return 0;
}