题目链接:https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=4008
题目大意:
如果问一个麦霸:“你在KTV里必唱的曲目有哪些?”得到的答案通常都会包含一首“神 曲”:古巨基的《劲歌金曲》。为什么呢?一般来说,KTV不会在“时间到”的时候鲁莽地把 正在唱的歌切掉,而是会等它放完。例如,在还有15秒时再唱一首2分钟的歌,则实际上多 唱了105秒。但是融合了37首歌曲的《劲歌金曲》长达11分18秒(5),如果唱这首,相当于多 唱了663秒!
假定你正在唱KTV,还剩t秒时间。你决定接下来只唱你最爱的n首歌(不含《劲歌金 曲》)中的一些,在时间结束之前再唱一个《劲歌金曲》,使得唱的总曲目尽量多(包含 《劲歌金曲》),在此前提下尽量晚的离开KTV。
输入n(n≤50),t(t≤109)和每首歌的长度(保证不超过3分钟(6)),输出唱的总曲目 以及时间总长度。输入保证所有n+1首曲子的总长度严格大于t。
大概意思是:有n首歌曲,和一首《劲歌金曲》,有时间t,问你最多能唱多少首歌,如果有几个方案,选择总时间最长的方案。(虽说t≤10^9,但由于所有n+1首曲子的总长度严格大于t,实际上t不会超过180n+678。 这样就可以转化为0-1背包问题了。)
思路:刚开始想到了用dp[i][j]表示前i首歌中唱j首的最短时间。然后找到第一个dp[n][j]输出,回来发现不对,dp[i][j]表示最短时间和最长时间都有问题,选择最大,可能让有解变无解,选择最小,可能导致无法取得最优解。
题解有两种思路:
思路一:
用即f[j]表示的是在j的时间恰好用完的情况下最多能唱多少首歌。那么再记录一个最多的歌曲数。再遍历一次就可以找到最长的时间了。
(为什么要刚好用完呢?:https://www.cnblogs.com/shi2015/p/4661971.html)
ps:气死我了,01背包写错了 dp[i][j]=max(dp[i-1][j-a[i]]+1, dp[i-1][j])写成 dp[i][j]=max(dp[i-1][j-a[i]]+1, dp[i][j]);debug一下午。还可以用滚动数组优化。
思路二:
跟普通的01背包有区别的是:要保证两个条件取得最优。那么我们可以把状态用结构体来描述。
我们令dp[i][j]==x 表示当决策完全前i个物品后(选或不选), 所选的总歌曲时长<=j时, 所得到的最优状态为x. (这里的x就不是平时我们所说的最长时间或最多歌曲数目了)
怎么理解最优状态为x这个事实呢? 假设有两种选择前i个歌曲的方法能使得决策完前i个物品且总时长<=j时的状态分别为x1 和x2.
1.那么如果x1状态的歌曲数目> x2状态的歌曲数目, 那么明显x1状态更优.
所以dp[i][j]应==x1.
2.如果x1状态的歌曲数目与x2的相等, 但是x2状态的时长 > x1状态时长,
那么此时x2状态更优. 所以dp[i][j]应==x2.
重载运算符就ok了。
经过上面的分析,我们可以用一个(具有歌曲数和总时长双属性的)结构体来表示一个状态. 且可以得到下面状态转移公式:
dp[i][j] = 最优( dp[i-1][j] , 在dp[i-1][j-t[i]]的基础上选择第i首歌后得到的新状态tmp )
所有dp初始化为0即可. 最终我们所求为dp[n][max_time]
//方法一
#include <bits/stdc++.h>
#define LL long long
using namespace std;
int dp[55][10000];
int a[55];
int main()
{
int t, CUT=1;
scanf("%d",&t);
while(t--)
{
int n, T, Max=0;
scanf("%d%d",&n,&T);
T--;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
memset(dp, 0, sizeof(dp));
for(int i=1;i<=n;i++)
{
for(int j=1;j<=T;j++)
{
if(j>=a[i]&&dp[i-1][j-a[i]]>=1||j-a[i]==0)
{
dp[i][j]=max(dp[i-1][j-a[i]]+1, dp[i-1][j]);
}
else
{
dp[i][j]=dp[i-1][j];
}
Max=max(dp[i][j], Max);
}
}
//滚动数组
//for(int i = 1; i <= n; i++)
//{
// for(int j = T; j >= a[i]; j--)
// {
// if(dp[j - a[i]] >= 1|| j == a[i]) //在j时间内可以唱完第i首歌。
// {
// dp[j] = max(dp[j], dp[j - a[i]] + 1);
// Max = max(Max, dp[j]);
// }
// }
//}
if(Max==0)
{
printf("Case %d: %d %d\n",CUT++, 1, (11)*60+18);
continue;
}
for(int i=T;i>=1;i--)
{
if(dp[n][i]==Max)
{
printf("Case %d: %d %d\n",CUT++, Max+1, i+(11)*60+18);
break;
}
}
}
return 0;
}
//方法二:
#include <bits/stdc++.h>
#define LL long long
using namespace std;
struct node
{
int n;//歌曲数
int t;//时间数
bool operator <(node &a)
{
if(n!=a.n)
{
return n<a.n;
}
else
{
return t<a.t;
}
}
};
node dp[10000];
int a[55];
int main()
{
int t, CUT=1;
scanf("%d",&t);
while(t--)
{
int n, T, Max=0;
scanf("%d%d",&n,&T);
T--;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
memset(dp, 0, sizeof(dp));
for(int i=1;i<=n;i++)
{
for(int j=T;j>=a[i];j--)
{
node x=node{dp[j-a[i]].n+1, dp[j-a[i]].t+a[i]};
if(dp[j]<x)
{
dp[j]=x;
}
}
}
printf("Case %d: %d %d\n",CUT++, dp[T].n+1,dp[T].t+678);
}
return 0;
}