第九章例题 E - Jin Ge Jin Qu hao

/*SE:wn------王宁*/

这道背包思路一点都不难想,大致实现也不难写。

最难的是什么?是在边界值的初始化和特殊情况的处理。

So, let's solve it!
 

/*SE:wn------王宁*/
#include<bits/stdc++.h>
using namespace std;
/*
dp[i][j]是说前i首歌在时刻j所能完成的歌的数目
一.
初始化,如果你是从第1首歌向后动态规划的
那么
dp[0]:
dp[0][0]一定要初始化成0,表明进行到第0首歌第0秒的时候能唱0首,
因为后面的dp[1][该首歌的长度]=max(dp[0][该首歌的长度],dp[0][0]+1),一定要有人给你接着对不?
dp[0][剩余]绝对不能初始化成0,为什么,比如第一首歌长度是80,到了81秒的时候,能说他等于0+1首吗?
不能,根据原则3——唱完如果在时限之内立马切歌,
所以不可能在第一秒的时候切歌——也就没有81秒的唱了一首歌的存在了
dp[其他][0]:
初始化为0是不必要的因为每次都会先向前继承0
其他dp值:置为-1,表明不可能到达的状态。只有当通过前面的dp或者dp[自己][0]来完成向正数的转变
二.
细节处理或者思路分析
1.对于那个二重循环,之前犯过的错是没有判断j>=len[i]就直接进行max的操作
在dev上他没有给我指示数组访问越界||程序崩溃真是呵呵呵……
对于它还要求之前的dp[i-1][j-len[i]]>=0,不难理解吧?
只有那个时间点在唱完立马切歌的条件下,配合了前面歌的长度能够到达这个时刻,
后面的歌才有资格在这个基础上进行加1比较
2.然后对于那个二重循环的内层循环边界就是一个很nice的点了,包括下面为什么取最大值的时候
为什么没有考虑dp[每一首歌][t]? 原因在于:
如果前面有歌能让这首歌加到刚好在t时刻结束,而这首歌必定小于678秒(劲歌金曲的长度)
如果直接唱劲歌金曲,是不是能到达同样的唱歌数目并且更晚回去?
3.ans=-1,p=0; ——第63行
pay attention!
刘汝佳大神是每次都比较&&记录最大值,这样到后面直接从后往前看谁等于这个值就直接printf的。
而在这里我是看谁是最大值然后记录位置,在全部遍历完之后再输出的。
你也不要小看了时刻靠前的时间点,不要认为时刻靠后就越有机会唱更多的歌
比如dp[n][60]=3,三首歌分别是10 20 30,dp[n][90]=1,这首歌是90,如果t是100,显然答案是4 678+60
pay attention again!
扯回来,ans=-1是为了防止什么呢?一首歌都没有能在t秒内唱完——不如&&最好直接唱劲歌金曲。
但是这时候只有dp[n][0]==0,你如果ans初值是0,
根据判断条件,p是不会有对应的值的(然而我的dev还是能输出678……,但是在vj上就过不了了)
所以要么把ans设为-1——遇到dp[n][0]就能知道p应该在0位置,或者一开始就把p置为0——保个底

总结:边界控制,数组初始化,思维。
*/
const int maxn=50+5;
int len[maxn];
int dp[maxn][678+5+180*50];
int main()
{
	int T,i,j,n,t,ans,p,kase=0;
	scanf("%d",&T);
	while(T--){
		scanf("%d%d",&n,&t);
		for(i=1;i<=n;i++){ scanf("%d", &len[i]);  }
		memset(dp,-1,sizeof(dp)); dp[0][0]=0;
		for(i=1;i<=n;i++)
		{
			for(j=t-1;j>=0;j--)
			{
				dp[i][j]=dp[i-1][j];
				if(j>=len[i]&&dp[i-1][j-len[i]]>=0)
				dp[i][j]=max(dp[i-1][j],dp[i-1][j-len[i]]+1);
			}
		} 
		ans=-1,p=0;
		for(j=t-1;j>=0;j--) if(ans<dp[n][j]){ ans=dp[n][j]; p=j; }
		printf("Case %d: %d %d\n",++kase,ans+1,p+678);
		
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/JXUFE_ACMer/article/details/81501605