题目大意:
每天有N个小时,每个小时进入熟睡状态可以获得体力值Ui,每N个小时必须休息B个小时,这B个小时可以随意分成几段,但是每段的第一个小时体力值是不计算的。现在叫你求解N个小时睡B个小时能得到的最大体力值。
分析:
先假设前后两天不相连,以第一个小时作为起点。设f(i,j,0/1)表示前i个小时里,睡了j个小时,其中第i个小时正在睡(1),或者没有睡(0)。可以得到状态转移方程:
f(i,j,0)=max:f(i-1,j,0),f(i-1,j,1)
f(i,j,1)=max:f(i-1,j-1,0),f(i-1,j-1,1)+Ui。前者表示第i-1小时没有睡觉,那么第i小时开始睡,后者表示第i-1小时正在睡觉,那么第i小时要计算体力。
由于我们假设第一个小时作为起点,所以第一个小时不论睡不睡,能得到的体力值都为0。所以边界为f(1,0,0)=f(1,1,1)=0,其余为负无穷。答案为max:f(N,B,0),f(N,B,1)。
但是考虑到可能存在第N小时睡觉,第二天第1小时计算体力,所以我们再做一次dp,这次就令第1小时正在睡觉并且计算了体力,也就意味着第一天第N小时必须在睡觉。只需改一下边界:f(1,1,1)=U1,其余的为负无穷。答案为f(N,B,1)。取两次的最大值即可。
这样设计足够覆盖所有的情况。
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<queue>
#include<map>
#include<set>
#include<cmath>
#include<vector>
#include<cstdlib>
#include<ctime>
using namespace std;
#define f(i,j,k) f[(i)&1][j][k]
const int MAXN=4000;
int T,N,B;
int val[MAXN];
int f[2][MAXN][2];
int main(){
// freopen("in.txt","r",stdin);
scanf("%d",&T);
while(T--){
scanf("%d%d",&N,&B);
for(int i=1;i<=N;i++)
scanf("%d",&val[i]);
int ans=0;
memset(f,0xcf,sizeof(f));//初值为负无穷
f[1][0][0]=f[1][1][1]=0;//边界:如果以1为起点,那么无论
for(int i=2;i<=N;i++)//睡不睡觉都不可能是熟睡
for(int j=min(i,B);j>=0;j--){
f(i,j,0)=max(f(i-1,j,0),f(i-1,j,1));
if(j-1>=0)f(i,j,1)=max(f(i-1,j-1,0),f(i-1,j-1,1)+val[i]);
}//如果i-1没有入睡,那么i即使睡了不能计数
ans=max(f(N,B,0),f(N,B,1));
memset(f,0xcf,sizeof(f));
f[1][1][1]=val[1];//假设第1小时熟睡,那么第N小时必然在睡
for(int i=2;i<=N;i++)
for(int j=min(i,B);j>=0;j--){
f(i,j,0)=max(f(i-1,j,0),f(i-1,j,1));
if(j-1>=0)f(i,j,1)=max(f(i-1,j-1,0),f(i-1,j-1,1)+val[i]);
}
ans=max(ans,f(N,B,1));
printf("%d\n",ans);
}
return 0;
}