【睡觉】解题报告

PROBLEM3 睡觉


问题描述:

为了提高程序解题能力,勤奋努力的QQ天天锯题到深夜,导致睡眠严重不足,可NOIP决赛就要来临了,必须要有良好的状态才行啊,因此QQ决定准备拿出一天时间,好好补补觉。

他把这一天等分成了n个时间段,在每个时间段睡觉能获得精神点数不尽相同,在第i段时间能获得V[i]的精神点数。

由于勤奋的QQ觉得整天都睡太堕落了,他决定最多只能睡m个时间段。至于其他的时间吗...那自然是勤奋地锯题...

有两点事情要特别提出注意:

1.QQ不可能一上床马上睡着,他在连续一段睡觉时间的第一个时间段不能获得此时的V。也就是说如果他在i...j中的所有时间都休息了,获得的精神点数为V[i+1]+...+V[j]

2.所有的时间段呈环形分布,也就是说第n个时间段之后为第1个时间段。

要求的自然是QQ最多能获得的精神点数之和。


输入格式:

第一行两个正整数n,m,意义如题所述

接下来n行,每行一个非负整数V[i]


输出格式:

一行,表示QQ最多能获得的精神点数之和


输入样例:

5 3

2

0

3

1

4


输出样例:

6


样例解释:

选择451三个时间段休息,在51时睡着,最大值为4+2=6


数据范围:

对于20%的数据n<=20

对于50%的数据n<=200

对于100%的数据n<=5000,m<=n,V[i]<=10000



这是一道动规,考试的时候50分,其他五组超时,原因是因为我看到是有环,就用了加倍,枚举起始点的方法,把时间复杂的增加了一维。

事实上,这道题环的表现不明显。可以只用一条链多一点的方法就可以了。


首先考虑一条链上。当前状态与总共睡了多长时间、现在的时刻、上一状态睡没有有关,因此状态是三维。

f[i][j][0~1]表示前i分钟,总共睡j分钟,上一分钟有没有在睡。

转移即: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]+v[i]}。


考虑枚举起点,对n条链进行DP,因为每一条链是独立的,因此每次DP完都必须给下次的DP赋初值(考试时这里调试了很久,至少半个小时)

(考虑一个反例,1时刻睡了,推出n时刻睡了的最优解。第二条链,n时刻睡了的最优解推出1时刻睡了的最优解。1时刻多睡了一次)

我就是不知如何处理这种情况,因此退了一步,进行了n次DP,超时了。


最优方法是,首先用普通方法以1为起点进行一次DP,求出一个解。第二步,1必须睡,以2为起点,n必须睡,求一个最优解。

第二种情况,即为从后面的一个时刻一直睡到1时刻,必须单独讨论。


当前状态只与上一阶段状态有关,可以进行01滚动。01滚动用了陈样说那个优化,把一个二维数组拆成多个,减少了乘法运算,不知道有没有用。


50分程序

后来发现有点错误,不知道为什么还能对。。就是f1[j][0]和f0[j][0]应该赋值为0,而不是-∞,因为他不是无效状态,

单步调试的时候,发现很多有效状态不见了,原来是这个原因。已经更正。

//#include <iostream>
//using std::cout;
//using std::cin;
#include <cstdio>
const long oo = 0x7fff0000;

long n;long m;
long f0[10010][2];
long f1[10010][2];
long v[10010];

int main()
{
	freopen("sleep.in","r",stdin);
	freopen("sleep.out","w",stdout);
	
	scanf("%ld%ld",&n,&m);
	for (long i=1;i<n+1;i++)
	{
		scanf("%ld",v+i);
		v[i+n] = v[i];
	}
	v[2*n+1] = v[1];
	v[2*n+2] = v[2];
	
	long ans = 0;
	
	for (long l=1;l<n+1;l++)
	{
		if ((l&1)==1)
		{
			f0[0][0]=0;
			for (long j=0;j<m+1;j++)
			{
				f0[j][0]=0;
				f0[j][1]=-oo;
				f1[j][1]=-oo;
				f1[j][0]=0;
			}
		}
		else
		{
			f1[0][0]=0;
			for (long j=0;j<m+1;j++)
			{
				f1[j][0]=0;
				f1[j][1]=-oo;
				f0[j][1]=-oo;
				f0[j][0]=0;
			}
		}
		for (long i=l;i<l+n;i++)
		{
			if ((i&1)==1)
			{
				for (long j=1;j<m+1;j++)
				{
					if (j-1<=i-1-l+1)
					{
						if (f1[j][1]<f0[j-1][0])
							f1[j][1]=f0[j-1][0];
						if (f1[j][1]<f0[j-1][1]+v[i])
							f1[j][1]=f0[j-1][1]+v[i];
					}
					if (f1[j][0]<f0[j][1])
						f1[j][0]=f0[j][1];
					if (f1[j][0]<f0[j][0])
						f1[j][0]=f0[j][0];		
				}
				for (long j=0;j<m+1;j++)
				{
					f0[j][1]=-oo;
					f0[j][0]=0;
				}
			}
			else
			{
				for (long j=1;j<m+1;j++)
				{
					if (j-1<=i-1-l+1)
					{
						if (f0[j][1]<f1[j-1][0])
							f0[j][1]=f1[j-1][0];
						if (f0[j][1]<f1[j-1][1]+v[i])
							f0[j][1]=f1[j-1][1]+v[i];
					}
					if (f0[j][0]<f1[j][1])
						f0[j][0]=f1[j][1];
					if (f0[j][0]<f1[j][0])
						f0[j][0]=f1[j][0];		
				}
				for (long j=0;j<m+1;j++)
				{
					f1[j][1]=-oo;
					f1[j][0]=0;
				}
			}
		}
		if ( ((l+n-1)&1) ==1)
		{
			if (ans<f1[m][0])
				ans = f1[m][0];
			if (ans<f1[m][1])
				ans = f1[m][1];
		}
		else
		{
			if (ans<f0[m][0])
				ans = f0[m][0];
			if (ans<f0[m][1])
				ans = f0[m][1];
		}
	}
	printf("%ld",ans);
	return 0;
}


AC程序

//#include <iostream>
//using std::cout;
//using std::cin;
#include <cstdio>
const long oo = 0x7fff0000;

long n;long m;
long f0[5010][2];
long f1[5010][2];
long v[5010];

int main()
{
	freopen("sleep.in","r",stdin);
	freopen("sleep.out","w",stdout);
	
	scanf("%ld%ld",&n,&m);
	for (long i=1;i<n+1;i++)
	{
		scanf("%ld",v+i);
	}
	v[n+1] = v[1];
	v[n+2] = v[2];
	if (n==m)
	{
		long min = oo;
		long sum = 0;
		for (long i=1;i<n+1;i++)
		{
			min <?= v[i];
			sum += v[i];
		}
		printf("%ld",sum-min);
		return 0;		
	}
	
	long ans = 0;
	
	long l = 1;
	{
		f0[0][0]=0;
		for (long j=0;j<m+1;j++)
		{
			f0[j][0]=0;
			f0[j][1]=-oo;
			f1[j][1]=-oo;
			f1[j][0]=0;
		}
		for (long i=1;i<n+1;i++)
		{
			if ((i&1)==1)
			{
				for (long j=1;j<m+1;j++)
				{
					if (j-1<=i-1-l+1)
					{
						if (f1[j][1]<f0[j-1][0])
							f1[j][1]=f0[j-1][0];
						if (f1[j][1]<f0[j-1][1]+v[i])
							f1[j][1]=f0[j-1][1]+v[i];
					}
					if (f1[j][0]<f0[j][1])
						f1[j][0]=f0[j][1];
					if (f1[j][0]<f0[j][0])
						f1[j][0]=f0[j][0];		
				}
				for (long j=0;j<m+1;j++)
				{
					f0[j][1]=-oo;
					f0[j][0]=0;
				}
			}
			else
			{
				for (long j=1;j<m+1;j++)
				{
					if (j-1<=i-1-l+1)
					{
						if (f0[j][1]<f1[j-1][0])
							f0[j][1]=f1[j-1][0];
						if (f0[j][1]<f1[j-1][1]+v[i])
							f0[j][1]=f1[j-1][1]+v[i];
					}
					if (f0[j][0]<f1[j][1])
						f0[j][0]=f1[j][1];
					if (f0[j][0]<f1[j][0])
						f0[j][0]=f1[j][0];		
				}
				for (long j=0;j<m+1;j++)
				{
					f1[j][1]=-oo;
					f1[j][0]=0;
				}
			}
		}
		if ( (n&1) ==1)
		{
			if (ans<f1[m][0])
				ans = f1[m][0];
			if (ans<f1[m][1])
				ans = f1[m][1];
		}
		else
		{
			if (ans<f0[m][0])
				ans = f0[m][0];
			if (ans<f0[m][1])
				ans = f0[m][1];
		}
	}
	l = 1;
	{
		for (long j=0;j<m+1;j++)
		{
			f0[j][0]=0;
			f0[j][1]=-oo;
			f1[j][1]=-oo;
			f1[j][0]=0;
		}
		f1[1][1] = v[1];
		for (long i=2;i<n+1;i++)
		{
			if ((i&1)==1)
			{
				for (long j=1;j<m+1;j++)
				{
					if (j-1<=i-1-l+1)
					{
						if (f1[j][1]<f0[j-1][0])
							f1[j][1]=f0[j-1][0];
						if (f1[j][1]<f0[j-1][1]+v[i])
							f1[j][1]=f0[j-1][1]+v[i];
					}
					if (f1[j][0]<f0[j][1])
						f1[j][0]=f0[j][1];
					if (f1[j][0]<f0[j][0])
						f1[j][0]=f0[j][0];		
				}
				for (long j=0;j<m+1;j++)
				{
					f0[j][1]=-oo;
					f0[j][0]=0;
				}
			}
			else
			{
				for (long j=1;j<m+1;j++)
				{
					if (j-1<=i-1-l+1)
					{
						if (f0[j][1]<f1[j-1][0])
							f0[j][1]=f1[j-1][0];
						if (f0[j][1]<f1[j-1][1]+v[i])
							f0[j][1]=f1[j-1][1]+v[i];
					}
					if (f0[j][0]<f1[j][1])
						f0[j][0]=f1[j][1];
					if (f0[j][0]<f1[j][0])
						f0[j][0]=f1[j][0];		
				}
				for (long j=0;j<m+1;j++)
				{
					f1[j][1]=-oo;
					f1[j][0]=0;
				}
			}
		}
		if ( (n&1) ==1)
		{
			if (ans<f1[m][1])
				ans = f1[m][1];
		}
		else
		{
			if (ans<f0[m][1])
				ans = f0[m][1];
		}
	}
	printf("%ld",ans);
	return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值