[luogu p1192] 台阶问题

传送门

台阶问题

题目描述

(N)级的台阶,你一开始在底部,每次可以向上迈最多(K)级台阶(最少(1)级),问到达第(N)级台阶有多少种不同方式。

输入输出格式

输入格式

两个正整数N,K。

输出格式

一个正整数,为不同方式数,由于答案可能很大,你需要输出(ans mod 100003)后的结果。

输入输出样例

输入样例 #1

5 2

输出样例 #1

8

说明

对于(20\%)的数据,有(N ≤ 10, K ≤ 3);
对于(40\%)的数据,有(N ≤ 1000);
对于(100\%)的数据,有(N ≤ 100000,K ≤ 100)

分析

这是一道递推题。遇到这种题,二话不说打草稿找规律。
让我们来看看(k)取不同值的时候的结果:

k = 2: 1 2 3 5 8 13 21 34 55
k = 3: 1 2 4 7 13 24 44 81 149
k = 4: 1 2 4 8 15 29 56 108 208
k = 5: 1 2 4 8 16 31 61 120 236
k = 6: 1 2 4 8 16 32 63 125 248

(dp_i)为爬(i)级阶梯的答案,那我们就可以得到一个规律:
(n le k)的时候,(dp_i = 2^{i-1})
(n > k)的时候,(dp_i = sum_{j = i - k}^{i-1}dp_j)
如何证明?首先假设(n=5,k=2),那么最多爬两级。此时,爬一级有1种情况,爬两级有2种情况。那么爬3级呢?爬3级我们可以先爬两级再爬一级,或者先爬一级再爬两级(但是再爬的两级必须一次性爬完,否则方案会跟前者先爬2再爬1重复)。那么爬3级的方法数就是爬1级的方法数加上爬2级的方法数,也就是1+2=3。爬四级的同理,我们可以先爬三级再爬一级,也可以先爬两级再爬两级(同理,这两级必须一次性。)最终结果就是爬三级的方法数加上爬两级的方法数,也就是2+3=5。以此类推,每一级的方法数都是前两者的和。
那么最多3级呢?现在已经求出爬1级答案1,爬2级答案2,爬3级答案4.那么爬4级的情况,就是先爬三级再爬一级,先爬两级再爬两级,先爬一级再爬三级。那么,爬四级的方法数就是第一级的方法数加上第二级的方法数加上第三极的方法数。而以此类推,每一个级的方法数都是前三者的和。
最多4级同理,最多5级也是如此,我们就成功的证明了一个级的方法数等于前(k)级的方法数之和。
但是这是(n > k)的情况,不过(n = k)的时候,结果是(2^{k-1})。这是咋回事呢?首先我们知道有一个定理(2^0+2^1+2^2+ldots+2^{n-1}+2^n = 2^{n+1}-1)。比如如果此时输入数据是(n=3,k=3),首先我们想如果(k=2),答案就是前两者加起来。这个时候也就是(2^0+2^1)。但是当(k=3)的时候,我们这里会新增一种方案:直接一步迈过去。这个时候的答案就是(2^0+2^1+1)了,根据前面的定理,这就是(2^2)

从特殊到一般,如果输入数据是(n = p, k = p),首先就得想如果(k = p - 1),答案就是前(p-1)个级数的方法数的和,也就是(2^0 + 2^1 + 2^2 + ldots + 2^{p-2}),但是此时(k = p)。我们可以直接迈过去,答案就得(+1),也就是(2^0+2^1+2^2+ldots+2^{p-2}+1),即(2^{p-1})。也就是说,(n=k)的时候,答案就是(2^{k-1})
叭叭叭这么多,代码就直接按照上面的式子推就好啦。时间复杂度(O(nk))
不过这事情还没完,这玩意儿还有优化的余地。求区间和用前缀和是个不错的方法,看此式子:当(n > k)的时候,(dp_i = sum_{j = i - k}^{i-1}dp_j)。我们可以灵魂推导一下:

[egin{aligned} dp_i & = sum_{j = i - k}^{i - 1}dp_j\ & = sum_{j = i - k}^{i - 2}dp_j + dp_{i - 1}\ &= sum_{j = i - k - 1}^{i - 2}dp_j + dp_{i - 1} - dp_{i - k - 1} \ & = dp_{i - 1} + dp_{i - 1} - dp_{i - k - 1} \ & = 2 imes dp_{i - 1} - dp_{i - k - 1} end{aligned} ]

于是乎,整个算法便变成了(O(n))

代码

朴素(O(nk))算法:

/*
 * @Author: crab-in-the-northeast 
 * @Date: 2020-02-28 16:18:47 
 * @Last Modified by: crab-in-the-northeast
 * @Last Modified time: 2020-02-28 16:24:45
 */
#include <iostream>
#include <cstdio>

int n,k;
int dp[100005];
const int mod = 100003;

int main() {
    scanf("%d%d",&n,&k);
    dp[1] = 1;
    for(int i = 2; i <= k; i++)
        dp[i] = dp[i - 1] * 2 % mod;
    for(int i = k + 1; i <= n; i++)
        for(int j = i - k; j < i; j++)
            dp[i] += dp[j] % mod;
    printf("%d
",dp[n] % mod);
    return 0;
}

巧妙的(O(n))做法:

/*
 * @Author: crab-in-the-northeast 
 * @Date: 2020-02-28 16:18:47 
 * @Last Modified by: crab-in-the-northeast
 * @Last Modified time: 2020-03-01 23:28:48
 */
#include <iostream>
#include <cstdio>

int n,k;
int dp[100005];
const int mod = 100003;

int main() {
    scanf("%d%d",&n,&k);
    dp[0] = dp[1] = 1;
    for(int i = 2; i <= k; i++)
        dp[i] = dp[i - 1] * 2 % mod;
    for(int i = k + 1; i <= n; i++)
        dp[i] = (dp[i - 1] * 2 - dp[i - k - 1])% mod;
    printf("%d
",(dp[n] + mod) % mod);
    return 0;
}

评测结果

AC 100,朴素(O(nk))做法:R31166063
AC 100,递推(O(n))做法:R31278504

原文地址:https://www.cnblogs.com/crab-in-the-northeast/p/luogu-p1192.html