【SSL 1479】不知道该叫啥
题目大意:
求有多少长度为 \(n\) 的正整数序列,满足任意两个相邻的数的乘积不超过 \(M\)。
正文:
考虑用动态规划,设 \(f_{i,j}\) 表示第 \(i\) 位填 \(j\) 时的方案数,则转移方程是:
\[f_{i,j}=\sum_{k=1}^{\frac{M}{j}}f_{i-1,k}
\]
将状态滚成一维再用前缀和:
\[f_j=sum_{\frac{M}{j}}
\]
对其进行整除分块(分个段)就可以快速预处理出前缀和 \(sum_j=sum_{j-1}+len\),\(len\) 值为当前一段的长度,最后转移方程为当前段长度乘上当前段价值。我们无法直接知道当前段的价值,而因为两点 \(i,j\) 若 \(i\times j\leq M\),那 \(i\leq \frac{M}{j}\),既然 \(i,j\) 是一一对应的(可互换),所以一定在段数减去 \(j\) 加一里,当前段价值也就知道了。
代码:
int main()
{
scanf ("%d%d", &n, &m);
a[1] = m;
for (register int l = 1, r; l <= m; l = r + 1)
{
r = m / (m / l);
len[++i] = min(r - l + 1, m - l + 1),
sum[i] = sum[i - 1] + len[i];
}
for (int k = 2; k <= n; k++)
{
for (int j = 1; j <= i; j++)
f[j] = sum[i - j + 1] * len[j] % mod;
for (int j = 1; j <= i; j++)
sum[j] = (sum[j - 1] + f[j]) % mod;
}
printf("%lld", sum[i]);
return 0;
}