CF414B Mashmokh and ACM 题解

题意概述

给定 \(n,k \ (1\le n,k\le 2000)\) ,求使用 \(1,2,...,n\) 可以构造的长度为 \(k\) 的子序列个数(允许重复)。其中子序列 \(1 \le b_1,b_2,\dots,b_k \le n\) 满足 \(b_{i-1}|b_i\ (\ 2\le i\le n)\)

思路

最长子序列数量的变形问题,固定子序列长度。于是定义 \(f[i][k]\) 表示以 \(i\) 结尾长度为 \(k\) 的子序列个数,不难发现

\[f[i][k]=\sum_{j|i}f[j][k-1] \]

  • \(j=1,2,...,n\)
  • \(f[i][1]=1, \ i\in[1,n]\)

三重循环暴力枚举实现该 DP 的时间复杂度为 \(O(n^2k)\),根据题目数据,当 \(n=k=2000\) 时,循环次数高达 \(8 \times 10^9\),无法满足题目要求。

优化 1

不难发现,若 \(j|i\),则 \(\exists t\),使得 \(j\cdot t = i\),那么有 \(1\le \min(j,t)\le \sqrt{i}\),这表明只需枚举到 \(\sqrt{i}\) 即可。特别地,若 \(j=\sqrt{i}\),需特判。

代码如下:

#include <bits/stdc++.h>
using namespace std;
const int MOD = 1'000'000'007;

int main() {
    ios::sync_with_stdio(0), cin.tie(0);

    int n, k;
    cin >> n >> k;
    vector f(n + 1, vector<int>(k + 1));
    for (int i = 1; i <= n; i ++) f[i][1] = 1;
    for (int u = 2; u <= k; u ++) {
        for (int i = 1; i <= n; i ++) {
            for (int j = 1; j <= i / j; j ++) {
                if (i % j == 0) {
                    f[i][u] = (f[i][u] + f[j][u - 1]) % MOD;
                    if (j != i / j) {
                        f[i][u] = (f[i][u] + f[i / j][u - 1]) % MOD;
                    }
                }
            }
        }
    }
    int ans = 0;
    for (int i = 1; i <= n; i ++) {
        ans = (ans + f[i][k]) % MOD;
    }
    cout << ans;
    return 0;
}

复杂度分析

上述代码的三层循环次数可写作

\[k \sum_{i=1}^{n}\sqrt{i} \]

而当 \(n\to \infty\) 时,内存循环

\[\sum_{i=1}^{n}\sqrt{i}\approx \int_{1}^{n}\sqrt{x}\,dx=\frac{2}{3}x^{3/2}\Big|_{1}^{n}=\frac{2}{3}(n^{3/2}-1) \]

故时间复杂度为

\[O(k\cdot n^{3/2}) \]

优化 2

换一种思考方式,不妨对每个 \(j\) 取枚举它的倍数 \(i\)
代码如下:

#include <bits/stdc++.h>
using namespace std;
const int MOD = 1'000'000'007;

int main() {
    ios::sync_with_stdio(0), cin.tie(0);

    int n, k;
    cin >> n >> k;

    vector f(n + 1, vector<int>(k + 1));

    for (int i = 1; i <= n; i ++) f[i][1] = 1;
    for (int u = 2; u <= k; u ++) {        
        for (int j = 1; j <= n; ++ j) {
            for (int i = j; i <= n; i += j) {
                f[i][u] = (f[i][u] + f[j][u - 1]) % MOD;
            }
        }
    }

    long long ans = 0;
    for (int i = 1; i <= n; ++ i) {
        ans = (ans + f[i][k]) % MOD;
    }

    cout << ans;
    return 0;
}

复杂度分析

上述代码的循环次数可被写作

\[k\cdot \sum_{j=1}^{n} \Big\lfloor\frac{n}{j}\Big\rfloor \]

\(n\to \infty\)

\[\sum_{j=1}^{n}\Big\lfloor\frac{n}{j}\Big\rfloor\approx n\sum_{j=1}^n \frac{1}{j}=nH_n \]

其中调和级数

\[H_n= \Theta(\log n) \]

故时间复杂度为

\[O(k n\log n) \]

posted @ 2026-01-29 12:54  uvwijk  阅读(3)  评论(0)    收藏  举报