1 条题解

  • 0
    @ 2025-8-24 22:18:22

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar OMG_wc
    幻想家协会会长

    搬运于2025-08-24 22:18:22,当前版本为作者最后更新于2020-03-08 09:52:40,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    本题就是正整数拆分问题,很容易想到一种 O(n2)O(n^2) 的 DP:

    fi,jf_{i,j} 表示用了前 ii 个数和为 jj 的方案数,那么 fi,j=fi1,j+fi,jif_{i,j}=f_{i-1,j}+f_{i,j-i},初始时 f0,0=1f_{0,0}=1

    这其实就是完全背包,但只能拿 7070 分(也可能乱搞到 8080)。

    那么怎么办呢?我们采用分块的思路,令 m=nm=\sqrt n ,对于 i<mi<m 的数采用以上完全背包来求,这部分时间复杂度为 O(nn)O(n\sqrt n)

    而对于大于等于 mm 的数,采用另外一种 DP:

    gi,jg_{i,j} 表示用了 ii 个大于等于 mm 的数和为 jj 的方案数,那么 gi,j=gi1,jm+gi,jig_{i,j}=g_{i-1,j-m}+g_{i,j-i},初始时 g0,0=1g_{0,0}=1

    • 转移方程的第一项 gi1,jmg_{i-1,j-m} 表示在拆分序列中增加一个数 mm
    • 转移方程的第二项 gi,jig_{i,j-i} 表示把当前拆分序列的每个数都加上 11

    ii 最大为 m\sqrt m,因而这一步时间复杂度为 O(nn)O(n\sqrt n)

    如果觉得不好理解,可以随便举个例子:

    假设当前 m=1m=1,你要拆的数是 1111,其中一种方案是 11=5+2+2+211=5+2+2 +2。初始拆分序列为空,现在有两种操作:操作 11 是在拆分序列中增加一个数 11,操作 22 是把当前序列中每个数都增加 11 。那么该方案对应的操作序列为:1222111212221112

    容易发现,一种拆分方案和一种操作序列是一一对应的,因此这样 DP 不会重复也不会遗漏。

    最后一步,就是把分块的两部分结合一下。枚举第一个部分的总和为 jj,那第二个部分的总和就为 njn-j,把两者的方案数乘起来记入答案中,即为 $\sum\limits_{j=0}^n (f_{m-1,j}\times \sum\limits_{i=0}^{m} g_{i,n-j})$。

    那么本题总时间复杂度为 O(nn)O(n\sqrt n) ,代码如下:

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 100005;
    
    int f[N];
    int g[400][N];
    int main() {
        int n, p;
        cin >> n >> p;
        int m = sqrt(n) + 1;
        f[0] = 1;
        for (int i = 1; i < m; i++) {
            for (int j = i; j <= n; j++) {
                f[j] += f[j - i];
                f[j] %= p;
            }
        }
        g[0][0] = 1;
        for (int i = 1; i < m; i++) {
            for (int j = i; j <= n; j++) {
                g[i][j] = g[i][j - i];
                if (j >= m) g[i][j] += g[i - 1][j - m];
                g[i][j] %= p;
            }
        }
        int ans = 0;
        for (int i = 0; i <= n; i++) {
            LL sum = 0;
            for (int j = 0; j < m; j++) sum += g[j][n - i];
            sum %= p;
            ans = (ans + f[i] * sum) % p;
        }
        cout << ans << endl;
        return 0;
    }
    
    • 1

    信息

    ID
    4347
    时间
    2000ms
    内存
    256MiB
    难度
    6
    标签
    递交数
    0
    已通过
    0
    上传者