1 条题解
-
0
自动搬运
来自洛谷,原作者为

OMG_wc
幻想家协会会长搬运于
2025-08-24 22:18:22,当前版本为作者最后更新于2020-03-08 09:52:40,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
本题就是正整数拆分问题,很容易想到一种 的 DP:
记 表示用了前 个数和为 的方案数,那么 ,初始时 。
这其实就是完全背包,但只能拿 分(
也可能乱搞到 分)。那么怎么办呢?我们采用分块的思路,令 ,对于 的数采用以上完全背包来求,这部分时间复杂度为 。
而对于大于等于 的数,采用另外一种 DP:
记 表示用了 个大于等于 的数和为 的方案数,那么 ,初始时 。
- 转移方程的第一项 表示在拆分序列中增加一个数 。
- 转移方程的第二项 表示把当前拆分序列的每个数都加上 。
最大为 ,因而这一步时间复杂度为 。
如果觉得不好理解,可以随便举个例子:
假设当前 ,你要拆的数是 ,其中一种方案是 。初始拆分序列为空,现在有两种操作:操作 是在拆分序列中增加一个数 ,操作 是把当前序列中每个数都增加 。那么该方案对应的操作序列为:。
容易发现,一种拆分方案和一种操作序列是一一对应的,因此这样 DP 不会重复也不会遗漏。
最后一步,就是把分块的两部分结合一下。枚举第一个部分的总和为 ,那第二个部分的总和就为 ,把两者的方案数乘起来记入答案中,即为 $\sum\limits_{j=0}^n (f_{m-1,j}\times \sum\limits_{i=0}^{m} g_{i,n-j})$。
那么本题总时间复杂度为 ,代码如下:
#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
- 上传者