题目链接:活动 - 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;
}