(第九届蓝桥杯省赛)耐摔指数(动态规划)

这篇博客介绍了如何使用动态规划和双指针优化方法解决一个关于手机耐摔指数测试的问题。题目中提到,测试次数是按手机计算的,手机数量用于增加测试容错率。通过分析,建立了状态转移方程,并用O(n^2)的时间复杂度初始解法。然后,通过优化将复杂度降低到O(n)。博客详细阐述了优化过程,并给出了优化后的代码实现。
摘要由CSDN通过智能技术生成

题目链接:活动 - AcWing

输入样例:

7

输出样例:

3

分析:首先说明一个理解易错点:测试次数是按照单个手机算的,也就是说假如两个手机同时测耐摔指数那么这样算2次,手机个数只是为了增加测试的容错率,假如只有一个手机,那么我们只能从高度为1的地方往上一层一层测试,因为这样要避免手机摔坏而导致无法继续测试,而有两个手机的话我们就可以让第一个手机在任意高度测试,因为还有另一个手机作为备用机。

下面来对这道题目做下分析:

f[i][j]表示有i个手机在高度为j的塔上测试耐摔指数所需要的最少测试次数

由状态的表示含义显然有f[1][i]=i,答案是f[3][n]

那怎么进行状态转移呢?对于f[i][j],假如我们拿第1个手机在第k层测试,这个时候有两种结果,第一种结果是手机摔坏,那么这个时候只剩了i-1个手机,且我们可以知道耐摔指数一定小于等于k,那么剩余需要的最小测试次数就是f[i-1][k-1],加上这一次的测试次数,总的测试次数就是f[i-1][k-1]+1,那如果要是没有摔坏,那这个时候还是i个手机,且我们知道耐摔指数一定大于等于k,在耐摔指数为[k,n]的区间测试和在耐摔指数为[0,n-k]的区间测试是等价的,那么总的测试次数就是f[i][n-k]+1,由于我们要考虑最坏情况,也就是说我们要取这两种情况的最大值,我们唯一能选择的就是在第几层进行测试,也就是k值,所以我们可以遍历所有的k<=n,求所有最坏情况的最小值,这样就可以求得答案,总的复杂度是O(n^2)

下面是代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<queue>
#include<stack>
#include<cstring> 
#include<map>
using namespace std;
const int N=10003;
int f[5][N];//f[i][j]表示有i个手机在高度为j的塔上测试耐摔指数所需要的最少测试次数 
int main()
{
	int n;
	cin>>n;  
	memset(f,0x3f,sizeof f);//初始化
	for(int i=1;i<=n;i++) f[1][i]=i;//初始化
	f[1][0]=f[2][0]=f[3][0]=0;//初始化 
	for(int i=1;i<=n;i++)
	for(int j=1;j<=i;j++)
		f[2][i]=min(f[2][i],max(f[2][j-1],f[1][i-j])+1);
	for(int i=1;i<=n;i++)
	for(int j=1;j<=i;j++)
		f[3][i]=min(f[3][i],max(f[3][j-1],f[2][i-j])+1);	
	cout<<f[3][n];
	return 0;
}

我们能不能对上面这种做法进行优化呢?答案是能,以对两个手机的状态的更新为例,我们注意到上面的动态转移方程中在i不变j递增时,f[2][j-1]是递增的,而f[1][i-j]是递减的,而我们要找到的那个答案就是f[2][j-1]和f[1][i-j]最接近的时候,因为若j偏小,则f[1][i-j]会偏大,否则f[2][j-1]会偏大,我们取的是两者的最大值,所以最优情况一定是两者最接近的时候,我们可以直接用一个双指针优化掉里面的那层循环,这样复杂度就变为了O(n),细节见代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<queue>
#include<stack>
#include<cstring> 
#include<map>
using namespace std;
const int N=10003;
int f[5][N];//f[i][j]表示有i个手机在高度为j的塔上测试耐摔指数所需要的最少测试次数 
int main()
{
	int n;
	cin>>n;
	memset(f,0x3f,sizeof f);//初始化
	for(int i=1;i<=n;i++) f[1][i]=i;//初始化
	f[1][0]=f[2][0]=f[3][0]=0;//初始化 
	int j=1;
	for(int i=1;i<=n;i++)
	{
		while(j<i&&f[2][j-1]<f[1][i-j]) j++;
		if(f[2][j-1]<f[1][i-j])
			f[2][i]=f[1][i-j]+1;
		else
			f[2][i]=f[2][j-1]+1;
	}
	j=1;
	for(int i=1;i<=n;i++)
	{
		while(j<i&&f[3][j-1]<f[2][i-j]) j++;
		if(f[3][j-1]<f[2][i-j])
			f[3][i]=f[2][i-j]+1;
		else
			f[3][i]=f[3][j-1]+1;
	}
	cout<<f[3][n];
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值