Everything that kills me makes me feel alive.

SP283 NAPTIME - Naptime 题解

双倍经验好评
题目链接

蒟蒻第一次用这样的方式做环上dp,实在是想记录和总结一下。

环上dp的做法

  1. 我们将环切成一个链之后,倍长该链做dp即可做到从每一位出发,可参考P1880石子合并

  2. 我们抢先将环切成一条链,然后枚举断点的状态,即为这道SP283

条件

  • \(n\) 时间内总共睡 \(b\) 小时。

  • 每一段睡觉区间第一段时间不参与贡献。

思路

这是一个环上dp,由于 \(n\) 有点小大,我们采取枚举断点状态的方式。

状态转移方程

\(dp_{i,j,1/0}\) 表示 \(i\) 时刻睡了 \(j\) 小时,\(1\) 表示睡了。

\[dp_{i,j,0}=\max(dp_{i-1,j,0},dp_{i-1,j,1}) \]

没睡则直接延续上一个时刻。

\[dp_{i,j,1}=\max(dp_{i-1,j-1,0},dp_{i-1,j-1,1}+a_i) \]

睡了讨论上一个时刻的情况,上一个时刻没睡则本时刻为第一段时间,不参与贡献。

断点讨论

  1. \(n\) 时刻没睡,那么 \(i\) 时刻要么睡,要么为第一段时间。
dp[1][0][0]=0,dp[1][1][1]=0;
  1. \(n\) 时刻睡了,那么 \(i\) 时刻睡了可参与贡献,由于这是一个环,我们会在后面状态转移的时候到达 \(n\) 同样会有 \(n\) 时刻睡了的情况,所以不用担心 \(n\) 时刻的状态没有记录。
dp[1][0][0]=0,dp[1][1][1]=a[1];

code

//dp[i][j][0]=max(dp[i-1][j][0],dp[i-1][j][1]);
//dp[i][j][1]=max(dp[i-1][j-1][0],dp[i-1][j-1][1]+a[i]);
//i-1没睡,i睡了也没用
//i时刻,已经睡了j小时,当前睡没睡 
#include<bits/stdc++.h>
//define int long long //不要开longlong,会mle。
using namespace std;
const int N=3840;

template<class T> T read(T &x){
	char c=getchar();bool f=0;x=0;
	while(!isdigit(c)) f|=c=='-',c=getchar();
	while(isdigit(c)) x=(x<<1)+(x<<3)+(c^48),c=getchar();
	return f?-x:x;
} 

int T,n,b,ans=-1;
int a[N];
int dp[N][N][2]; //第一篇题解状转更牛哦,新手学习这个没关系

int main(){
	read(T);
	while(T--){
		read(n),read(b);
		for(int i=1;i<=n;i++) read(a[i]);
		memset(dp,-0x3f,sizeof(dp));//n时刻没睡
		dp[1][0][0]=0;
		dp[1][1][1]=0;
		for(int i=2;i<=n;i++){
			for(int j=0;j<=min(i,b);j++){
				dp[i][j][0]=max(dp[i-1][j][0],dp[i-1][j][1]);//直接顺延
				dp[i][j][1]=max(dp[i-1][j-1][0],dp[i-1][j-1][1]+a[i]); 
			}
		}
		ans=dp[n][b][0];
		memset(dp,-0x3f,sizeof(dp));////n时刻睡了
		dp[1][0][0]=0;
		dp[1][1][1]=a[1];
		for(int i=2;i<=n;i++){
			for(int j=0;j<=min(i,b);j++){
				dp[i][j][0]=max(dp[i-1][j][0],dp[i-1][j][1]);
				dp[i][j][1]=max(dp[i-1][j-1][0],dp[i-1][j-1][1]+a[i]);
			}
		}
		ans=max(ans,dp[n][b][1]);
		printf("%d\n",ans);
	}
	
}
/*
5 3
2
0
3
1
4
*/
/*
6
*/

蒟蒻拙见,请指教

posted @ 2022-07-25 20:13  wind_seeker  阅读(81)  评论(0)    收藏  举报
Live2D