DP的优化和根号分治: 跑步
NOI-Online 2020 跑步
题意
求整数可重复划分方案数, .
状态 的表示数字 的划分中, 最大的数是 的方案数.
如 5 5 2 1
就是一个包括在 中的一个划分方案.
每个状态 由所有 的总和组成. 可以理解成在 的每一个划分方案中加上一个元素 得到一个对应的 的划分.
的状态, 的转移, 复杂度 .
for (register unsigned i(1); i <= n; ++i) {
f[i][i] = 1;
}
for (register unsigned i(2); i <= n; ++i) {
for (register unsigned j(1); j < i; ++j) {
for (register unsigned k(1); k <= j; ++k) {
f[i][j] += f[i - j][k];
if(f[i][j] >= p) f[i][j] -= p;
}
}
}
for (register unsigned i(1); i <= n; ++i) {
Ans += f[n][i];
if(Ans >= p) Ans -= p;
}
改变状态的定义, 表示所有元素小于等于 的 的方案数, 也就是 状态的前缀和.
这时转移可以理解为, 表示的单个元素小于等于 , 组成 的方案中, 其元素一定小于 , 所以也应该统计入 中.
而 则是将元素在 以内的 的划分中, 加入一个元素 得到的方案.
这样可以把转移优化到 , 状态仍是 , 时间复杂度 .
f[1][1] = 1;
for (register unsigned i(1); i <= n; ++i) {
f[0][i] = 1;
}
for (register unsigned i(1); i <= n; ++i) {
for (register unsigned j(1); j <= i; ++j) {
f[i][j] = f[i][j - 1] + f[i - j][j];
if(f[i][j] >= p) f[i][j] -= p;
}
for (register unsigned j(i + 1); j <= n; ++j) {
f[i][j] = f[i][j - 1];
}
if(f[i][i] >= p) f[i][i] -= p;
}
printf("%u\n", f[n][n]);
发现对于第二维只会用到 和 可以滚动数组, 只保留第一维, 对整个数组进行 次转移.
状态 , 空间复杂度 , 转移 , 时间复杂度 .
之所以能多得 , 是因为时空复杂度同阶的时候, 往往空间更容易炸, 于是优化了空间.
f[0] = 1;
for (register unsigned j(1); j <= n; ++j) {
for (register unsigned i(j); i <= n; ++i) {
f[i] += f[i - j];
if(f[i] >= p) f[i] -= p;
}
}
printf("%u\n", f[n]);
根号分治, 设 , 先将所有 的 求出来, 这时的 表示单个元素不超过 的 的划分方案数. (除了 的上界以外和 完全相同)
n = RD(), p = RD(), Sq = sqrt(n) + 1;
f[0] = 1;
for (register unsigned j(1); j <= Sq; ++j) {
for (register unsigned i(j); i <= n; ++i) {
f[i] += f[i - j];
if(f[i] >= p) f[i] -= p;
}
}
然后再定义一个数组 , 表示包含 个非 元素的 的划分, 其中, .
转移也很简单 是 个元素的 的划分, 所有这种划分加上一个元素 就是一个 个元素, 的划分. 是 个元素, 的划分, 给这种划分的每个元素加 , 得到的就是一个最小元素大于 的 个元素的 的划分. 这两种情况互不重复, 因为前者最小元素是 , 后者最小元素大于 . 也不会遗漏, 因为任何可行的划分都是可以由 的划分和这两种转移转移而来的.
g[0][0] = 1;
for (register unsigned i(1); i <= Sq; ++i) {
for (register unsigned j(i); j <= n; ++j) {
g[i][j] = g[i - 1][j - 1] + g[i][j - i];
if(g[i][j] >= p) g[i][j] -= p;
}
}
然后统计答案.
我们把一个划分中的元素分成两类: 和 .
枚举 作为 的元素个数, 因此 , .
枚举 作为 的元素总和, 因此 .
对于每一个 , 作为约束, 求合法 的划分方案数.
分别讨论两种元素, 首先是 的元素, 它们的总和应该是 , 前面求出了 数组, 因此元素不大于 的 的划分就是 .
接下来是 的元素, 因为每个元素都大于 , 所以我们可以只考虑它们比 多出来的部分. 所以相当于求包含 个元素的 的划分, 也就是 .
因为两种元素对 和 的划分中, 不同种类的元素大小一定不同, 所以互不干扰, 使用乘法原理统计答案即可:
for (register unsigned i(0); i * (Sq + 1) <= n; ++i) {
for (register unsigned j(i * (Sq + 1)); j <= n; ++j) {
Ans = ((unsigned long long)g[i][j - Sq * i] * f[n - j] + Ans) % p;
}
}
对于 , 状态数 , 转移 , 时间复杂度 .
对于 , 状态数 , 转移 , 时间复杂度 .
对于 , 时间复杂度 .
总复杂度 .
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具