1 条题解

  • 1
    @ 2024-7-28 16:24:19

    从点 nn 开始,只有两种跳法:跳到 n1n-1 处,或者跳到 nn 的因子 dd 处. 跳到 n1n-1 处后等同于从 n1n-1 处开始,但跳到 dd 和从 dd 开始有一定区别,因为跳完后还可以往前跳(只要不超过 n1n-1 即可).

    (上图黑线表示第一步、蓝线表示第二步,直线表示从 ii 跳到 i±1i\pm1 的类型,曲线从 ii 跳到 ii 的因子的类型.)

    我们用 fi,jf_{i,j} 表示从 ii 出发到 11,且经过的点在 [1,j][1,j] 范围内的方案数,我们要求的答案即为 fn,nf_{n,n}. 根据前面的分析,可以得到:

    fi,i=fi1,i1+difd,i1f_{i,i} = f_{i-1,i-1} + \sum_{d\mid i}f_{d,i-1}

    那么对于 j>ij>ifi,jf_{i,j} 怎么求呢?首先根据定义 fi,j1f_{i,j-1} 的方案对 fi,jf_{i,j} 有贡献.

    如果我们要到达 jj 点,必须是刚开始就从 ii 向前跳到 jj,到达 jj 点后,不能再到 j1j-1 或者 j+1j+1,只能跳到 jj 的因子处离开,且这个因子必须小于 ii.

    可以得到:

    fi,j=fi,j1+djfd,i1f_{i,j}=f_{i,j-1} + \sum_{d|j}f_{d,i-1}

    观察到我们推的两个式子形式相似,可以放在一个循环中,动态规划的边界即为 f1,i=1f_{1,i} = 1.

    对于每个数,我们可以提前预处理因子,算法的时间复杂度为 nn+n2d(n)n\sqrt n+n^2\cdot d(n),优化一下常数,把对 kk 取模改成减去 kk 即可通过.

    代码:

    #include <iostream>
    #include <vector>
    const int MAXN = 5005;
    using namespace std;
    int n;
    long long f[MAXN][MAXN], mod;
    vector<int> d[MAXN];
    int main() {
        cin >> n >> mod;
        for (int i = 1; i <= n; ++i) f[1][i] = 1;
        for (int i = 1; i <= n; ++i) {
            for (int j = 1; j * j <= i; ++j) {
                if (i % j == 0) {
                    d[i].push_back(j);
                    if (i / j != j) d[i].push_back(i / j);
                }
            }
        }
        for (int i = 2; i <= n; ++i) {
            f[i][i] = f[i - 1][i - 1];
            for (int j = i; j <= n; ++j) {
                f[i][j] += f[i][j - 1];
                for (int k : d[j]) {
                    if (k < i) {
                        f[i][j] += f[k][i - 1];
                        if (f[i][j] > mod) f[i][j] -= mod;
                    }
                }
            }
        }
        cout << f[n][n] % mod << endl;
        return 0;
    }
    

    信息

    ID
    19
    时间
    2000ms
    内存
    512MiB
    难度
    6
    标签
    递交数
    208
    已通过
    41
    上传者