LOJ6089. 小 Y 的背包计数问题 题解
本题是一个背包计数问题,发现 \(n \leq 10^5\),显然普通的多重背包计数不可取,需要优化。
本着多重背包计数时间复杂度成本较高,我们总是希望尽量不做多重背包计数,发现对于编号 \(i > \lfloor \sqrt{n} \rfloor\) 的物品,由于要装满背包,它们永远不可能用完,所以我们可以对其使用其它方法计数。故考虑一种类似于根号分治的思想,对编号不超过 \(\lfloor \sqrt{n} \rfloor\) 和超过该值的物品分类讨论。
- 对于编号 \(i \leq \lfloor \sqrt{n} \rfloor\) 的物品:
由于 \(\sqrt{n}\) 数量级在我们接受范围内,故考虑直接使用多重背包计数,设 \(f(i, j)\) 表明考虑只从前 \(i\) 个物品中取物品,取出的物品总体积为 \(j\) 的方案数,则不难列出方程:
可以使用滚动数组优化掉 \(i\) 那一维,又发现每次转移都是取 \(f(i - 1, j - i \times i), f(i - 1, j - i \times(i - 1)), \cdots, f(i - 1, j)\) 的值,可见每次取值都是各相邻 \(i\) 个位置取一次值,这样我们可以使用类似于前缀和优化的方式进行优化转移。(其实这里如果不使用滚动数组的话也可以,毕竟 \(i\) 那一维的大小已经被我们控制在了 \(O(\sqrt{n})\),而加上 \(j\) 那一维,空间复杂度为 \(O(n \sqrt{n})\) 也不会爆空间)
- 对于编号 \(i > \lfloor \sqrt{n} \rfloor\) 的物品:
由于此类物品无法取完,故不用考虑每种物品取了多少个。我们发现这类物品我们最多只能取 \(\lfloor \sqrt{n} \rfloor\) 个,故不妨设 \(g(i, j)\) 为取 \(i\) 个此类物品,取出的这 \(i\) 个物品总体积为 \(j\) 的方案数。我们考虑如何列递推式,要想知道 \(g(i, j)\),就需要知道其可以由哪些状态变化而来,注意这里提到的状态必须保证计数不重(前驱状态和后续状态一一对应)不漏(不会少考虑情况) 。考虑到这两点后我们就来寻找这些状态吧 qwq
首先容易想到的是取 \(i\) 个物品的状态一定是可以由取 \(i - 1\) 个物品的状态推出来的,但这时我们似乎需要枚举 \(g(i - 1, (i - 1) \times (\lfloor \sqrt{n} \rfloor - 1))\) 到 \(g(i - 1, j - \lfloor \sqrt{n} \rfloor - 1)\) 所有值,这显然还是会超时,但我们发现一个有趣的事情是,如果我们取了一个体积大于 \(\lfloor \sqrt{n} \rfloor + 1\) 的物品,我们可以将其看成先取了一个体积为 \(\lfloor \sqrt{n} \rfloor + 1\) 的物品,再通过某种方式使物品的总体积“增长”到我们要的目标 \(j\),考虑寻找增长的方式,由于前后状态必须一一对应,所以每次所有已选的物品的体积增长量必须相同,由于不能跳过某些状态,所以这个增长量只能为 1,所以我们就找到了一种增长的方法,使得这样计数不重不漏。
所以可以推出 \(g(i, j)\) 的状态有两种:一种是已经取了 \(i - 1\) 个物品,又取了一个体积为 \(\lfloor \sqrt{n} \rfloor + 1\) 的物品,达到了状态 \(g(i, j)\),即 \(g(i - 1, j - \lfloor \sqrt{n} \rfloor - 1)\);一种是我们上文中所述的,将取体积大于 \(\lfloor \sqrt{n} \rfloor + 1\) 的物品的情况进行分解,将其分解为第一种状态与“增长”这两步,由于第一步在前面的状态中肯定会被当成第一种状态算进答案中,故我们只需考虑如何利用“增长”统计这部分答案,而正如上文所述,增长是要求每一个已选物品体积均增加 1,所以总体积增加 \(i\),而已选物品数量是不会通过增长改变的,所以这一种可以推出 \(g(i, j)\) 的状态为 \(g(i, j - i)\)。
综上,状态转移方程为:
由于我们将物品按编号分类讨论,所以我们最终要使用乘法原理合并答案,我们设 \(F(i) = f(\lfloor \sqrt{n} \rfloor, i), G(i) = \sum_{j = 0}^{\lfloor \sqrt{n} \rfloor} \limits g(j, i)\),则答案为:
两部分时间复杂度均为 \(O(n \sqrt{n})\),故总时间复杂度也为 \(O(n \sqrt{n})\)。
下面是简短的代码qwq:
Code
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
const int maxn = 1e5 + 10;
const int p = 23333333;
int n, m, f[maxn], sum[maxn], g[320][maxn];
int main()
{
scanf("%d", &n);
m = sqrt(n);
f[0] = 1;
for (int i = 1; i <= m; ++i)
{
for (int j = 0; j < i; ++j)
sum[j] = f[j];
for (int j = i; j <= n; ++j)
sum[j] = (f[j] + sum[j - i]) % p;
for (int j = 0; j <= n; ++j)
{
if (j >= i * (i + 1))
f[j] = (sum[j] - sum[j - i * (i + 1)] + p) % p;
else
f[j] = sum[j];
}
}
g[0][0] = 1;
int ans = f[n];
for (int i = 1; i <= m; ++i)
{
for (int j = i * (m + 1); j <= n; ++j)//根据 g 的定义 g[i][i * (m + 1)] 之前数组的值都应为 0,所以直接跳过
{
g[i][j] = (g[i - 1][j - (m + 1)] + g[i][j - i]) % p;
ans = (ans + 1ll * f[n - j] * g[i][j] % p) % p;//这里直接统计答案,但需注意 f[n] 无法在循环中统计到
}
}
printf("%d\n", ans);
return 0;
}

浙公网安备 33010602011771号