蓝桥杯_2018_第9届_第四题_测试次数

第四题:测试次数

x星球的居民脾气不太好,但好在他们生气的时候唯一的异常举动是:摔手机。各大厂商也就纷纷推出各种耐摔型手机。x星球的质监局规定了手机必须经过耐摔测试,并且评定出一个耐摔指数来,之后才允许上市流通。x星球有很多高耸入云的高塔,刚好可以用来做耐摔测试。塔的每一层高度都是一样的,与地球上稍有不同的是,他们的第一层不是地面,而是相当于我们的2楼。如果手机从第7层扔下去没摔坏,但第8层摔坏了,则手机耐摔指数=7。特别地,如果手机从第1层扔下去就坏了,则耐摔指数=0。如果到了塔的最高层第n层扔没摔坏,则耐摔指数=n为了减少测试次数,从每个厂家抽样3部手机参加测试。某次测试的塔高为1000层,如果我们总是采用最佳策略,在最坏的运气下最多需要测试多少次才能确定手机的耐摔指数呢?
请填写这个最多测试次数。
注意:需要填写的是一个整数,不要填写任何多余内容。


答案: 19

解析:

注意:本题中手机是没有后效性的,即:没有扔坏的话可以当作新的继续扔

该题很容易让人想到二分,二分树的深度为 log2(1000+1) = 10 显然,只有3个手机没有办法间隔大距离(一层一层递增的扔可以)进行尝试10次,所以二分最少需要10个手机。

本题的正确思路应该是动态规划:

一. 根据变量制表:

                      图中白色空格中内容为(仅有i层楼时利用j个手机时题目所求的最佳策略最坏运气下的)摔手机次数

 表格中红色部分即为题目要求解,数量过大只能依靠机器操作,为此我们先人工填表找规律

  对应数据结构就是一个二维数组:int dp[1001][4] (从1开始)

二.完善表格:

1. 手机数量为1时:

 显然当手机仅有一个时,只能一层一层的摔,所以有几层(这里假设只有这么多层楼,要求确保能够再找到解)就要摔几次

对应代码: 

for(int i = 1 ; i<=1000 ; i++)
     dp[i][1] = i;

2. 手机数量为2时:

1.2楼显然和第一种情况一样,当存在3楼时,我们知道扔3次一定可以找到解(第3楼扔一次+前2楼扔的次数)也就是空格中可以填的最大值,那么最小值呢?由于我们有两个手机,可以冒险一点不用一层一层的扔。我们可以将3层楼分成2个区间,考虑到让两个手机都发挥作用,区间长度len>= 1 (显然分隔点在2楼,也就是说两个区间为:[1,2]和[2,3])   步骤:先在第2层楼扔一次,此时有两种情况:(见图)

损坏:说明楼层过高,我们就从2楼的下层继续尝试:(问题就转换成了求只有K-1层楼,j-1个手机时的次数+1) 2-1 = 1 如果在1楼损坏了的话就得到结果,此时摔的次数为:1楼一次+2楼一次 = 2次

未损坏:楼层不够高,从2楼的上层继续尝试(问题就转换成了还有N-K层楼j个手机时的次数+1:这里不是K+1,因为这里的楼层表示的是仅有的楼层数,当在第k层损坏后,仅需要测试的楼层的区间变成了[k+1,N],则区间长度为:N-(k+1)+1 = N-K ,N表示此时待填空对应的楼层数,K表示前一次扔的楼层) 3 - 2 = 1  如果在3楼损坏则说明耐摔指数为2,否则为3,此时摔的次数为: 2楼一次+3楼一次 = 2次

此时表为:

三.总结规律:

由于我们可以确定每个空的最大值为其前一楼层次数+1(例如:求2个手机,3楼的次数时,在求2楼的条件下在3楼再摔一次一定可以得到3楼的次数) 即  dp[i-1][j]+1

最小值有两种情况:

损坏:继续尝试共k-1层,手机数-1: dp[k-1][j-1]

未损坏:继续尝试共N-K层,手机数不变:dp[n-k][i]

最少次数从上述两种情况中产生,由于要求运气最差即:max(损坏情况次数,未损坏情况次数)

且两者都在第K层尝试过一次max(损坏情况次数+1,未损坏情况次数+1) 

即:max(dp[k-1][j-1]+1,dp[n-k][i]+1) => max(dp[k-1][j-1],dp[n-k][j])+1

max(dp[k-1][j-1],dp[n-k][i])+1 <= dp[i][j] <=dp[i-1][j]+1

最后结果存在dp[1000][3]中,由于我们填表的过程中,是按行优先填的(手机数一定情况下,楼层变化,这样做是因为每空最大值的确定依赖于它前面一空(手机数不变,楼层-1))所以实现的代码也是如此(手机数在外层循环):(注意:我们的策略必须是最优的,即使运气差,得到的只是可选项差,不会影响我们从可选项中挑选最优的解,即次数最少的解,所以我们需要判断当前选项哪一个最优即:min())

外层两个for是遍历整个表格,内层的k是在给定空(j,i)中尝试最优解

for(int i = 2 ; i<=3 ; i++)
    for(int j = 1 ; j<=1000 ; j++)
        {
         for(int k = 1  ; k<j ; k++)
              dp[j][i] = min(dp[j-1][i]+1,max(dp[k-1][i-1],dp[j-k][i])+1)
        }

这里是有问题的,因为最大值的确定不依赖于k,因此我们没有必要将其放在k的循环中,并且不能放在其中,否则每次更新都将以dp[j-1][i]+1作为依据,没有起到维护的效果。我们可以将最大值直接作为dp[i][j]的初值再利用min()维护,同时由于手机数从2开始(手机数为1时的情况已经赋过初值),内层的k直接从2开始即可:

for(int i = 2 ; i<=3 ; i++)
    for(int j = 1 ; j<=1000 ; j++)
        {
         dp[j][i] = dp[j-1][i]+1;
         for(int k = 2  ; k<j ; k++)
              dp[j][i] = min(dp[j][i],max(dp[k-1][i-1],dp[j-k][i])+1);
        }

四.完整代码:

#include<bits/stdc++.h>
#define MAX 1001

using namespace std;

int dp[MAX][5];
//dp[i][j]:在仅有i层楼时使用j个手机需要摔的最大次数

int main()
{
    //只有一个手机时,几层楼就要摔几次
    for(int i = 1 ; i<=1000 ; i++)
        dp[i][1] = i;

    // 两个及以上的手机时:
    for(int i = 2 ; i<=3 ; i++)
        for(int j = 1 ; j<=1000 ; j++)
        {
            //赋最大初值(楼层每增加一层,其需要摔的次数一定会小于等于其楼层数减一的次数+1)
            dp[j][i] = dp[j-1][i]+1;
            for(int k = 2  ; k<j ; k++)
                dp[j][i] = min(dp[j][i],max(dp[k-1][i-1],dp[j-k][i])+1);
        }
    cout<<dp[1000][3]<<endl;
    return 0;
}

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值