【动态规划】跳石板(逐步分析,详例)

跳石板题目描述:
小易来到了一条石板路前,每块石板上从1挨着编号为:1、2、3…
这条石板路要根据特殊的规则才能前进:对于小易当前所在的编号为K的 石板,小易单次只能往前跳K的一个约数(不含1和K)步,即跳到K+X(X为K的一个非1和本身的约数)的位置。 小易当前处在编号为N的石板,他想跳到编号恰好为M的石板去,小易想知道最少需要跳跃几次可以到达。

例如:
N = 4,M = 24:
4->6->8->12->18->24
于是小易最少需要跳跃5次,就可以从4号石板跳到24号石板

思路:

  • 这题我们用动态规划的思想,将N~M个石板看做成一个一维数组stepNum[i],其中数组中存储着N到当前位置的最小步数,如果是0说明i号石板无法经过。
  • 我们从N号石板开始进行遍历,记录他所能达到的所有下一个石板位置,并记录在stepNum[i]中,这样遍历完所有石板后,先不考虑其他,我们应该能得出:如果stepNum[i]中有的元素任然为0,则该石板不能经过。
  • 而在遍历的过程中,我们势必会遇到同一块石板有不同的步数到达,这时候就需要我们进行选择了,选择之前的到达该石板步数和当前步数+1两者最小的,这就是我们的动态规划状态转移方程:stepNum[divNum[j]+i]=min(stepNum[divNum[j]+i],stepNum[i]+1)
  • 经过遍历一次后得到结果,jumpNum[M]就是从N开始加,加到M的最少次数,如果是0,说明不可能加到M

例子:4—>24

  • i=4(2x2):4(1)->6(2)
  • i=5(无):stepNum[5]==0 continue;
  • i=6(2x3):4(1)->6(2)->8(3) or 4(1)->6(2)->9(3)
  • i=7(无):stepNum[7]==0 continue;
  • i=8(2x4):4(1)->6(2)->8(3)->10(4) or 4(1)->6(2)->8(3)->12(4)
  • i=9(3x3):4(1)->6(2)->9(3)->12(4)
  • i=10(2x5):4(1)->6(2)->8(3)->10(4)->12(5)
  • stepNum[12]=4,stepNum[10]+1=5,说明此时到达12号的石板的步数并非最优解,stepNum[12]=min(stepNum[12],stepNum[10]+1,stepNum[12]选二者最小值4。

详细代码:

#include<iostream>
#include<algorithm>
#include<vector>
#include<cmath>
using namespace std;

void divisorNum(int n,vector<int>& divNum)
{
    
    
    for(int i=2;i<=sqrt((double)n);i++)
    {
    
    
        if(n%i==0)
        {
    
    
            divNum.push_back(i);
            //当两个约数不等的时候,另一个加入
            if(n/i!=i)
                divNum.push_back(n/i);
        }
    }
}

int Jump(int N,int M)
{
    
    
    //存储N到达M需要的步数,将其全部初始化为0,0表示这个石板不能被经过,不为0,则表示之前经过
    vector<int> stepNum(M+1,0);
    //初始化开始N为1,N到N的步数为1
        stepNum[N]=1;
    //从N石板开始,以及更新i石板能到达的石板,并记录到达的步数
    for(int i=N;i<=M;i++)
    {
    
    
        //用来存储i能向前走的步数,即i的约数
        vector<int>divNum;
        //石板不能经过,跳过这次循环
        if(stepNum[i]==0)
        {
    
    
            continue;
        }
        divisorNum(i,divNum);
        //遍历i的约数,逐步尝试从i向前走divBum[j]步
        for(int j=0;j<(int)divNum.size();j++)
        {
    
    
            //divNum[j]:向前走的步数
            //divNum[j]+i:从i走divNum[j]步,当前到达的石板的下标,必须小于M
            //stepNum[divNum[j]+i]!=0:从i走divNum[j]步,到达的石板,此前为被经过
            if(divNum[j]+i<=M && stepNum[divNum[j]+i]!=0)
            {
    
    
                //进入循环,表明石板已经经过,则需要更新他的状态,将其更新为到达所需步数最少的状态
                //有点难理解举个例子:从4->6->9->12需要4步,
                //4->6->8->10->12则需要5步,我们选择4步。
                stepNum[divNum[j]+i]=min(stepNum[divNum[j]+i],stepNum[i]+1);
            }
            //石板未经过,则更新到达石板所需步数的信息(注意:此时不是最小步数!)
            else if(divNum[j]+i<=M)
            {
    
    
                stepNum[divNum[j] + i] = stepNum[i] + 1;
            }
        }
        
    }
    if(stepNum[M]==0)
        return -1;
    return stepNum[M]-1;
}

int main()
{
    
    
    int n,m;
    while(cin>>n>>m)
    {
    
    
        cout<<Jump(n, m)<<endl;
    }
    return 0;
}

最后感谢https://blog.csdn.net/shuffle_ts/article/details/91357800三毛的详解。

猜你喜欢

转载自blog.csdn.net/cckluv/article/details/110871444