Naptime(环形dp)

题目大意:

每天有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;
}

猜你喜欢

转载自blog.csdn.net/WWWengine/article/details/82839613