LOJ6089. 小 Y 的背包计数问题 题解

LOJ6089. 小 Y 的背包计数问题

本题是一个背包计数问题,发现 n105,显然普通的多重背包计数不可取,需要优化。

本着多重背包计数时间复杂度成本较高,我们总是希望尽量不做多重背包计数,发现对于编号 i>n 的物品,由于要装满背包,它们永远不可能用完,所以我们可以对其使用其它方法计数。故考虑一种类似于根号分治的思想,对编号不超过 n 和超过该值的物品分类讨论。

  • 对于编号 in 的物品:

由于 n 数量级在我们接受范围内,故考虑直接使用多重背包计数,设 f(i,j) 表明考虑只从前 i 个物品中取物品,取出的物品总体积为 j 的方案数,则不难列出方程:

f(i,j)=k=0if(i1,jk×i)(f(0,0)=1)

可以使用滚动数组优化掉 i 那一维,又发现每次转移都是取 f(i1,ji×i),f(i1,ji×(i1)),,f(i1,j) 的值,可见每次取值都是各相邻 i 个位置取一次值,这样我们可以使用类似于前缀和优化的方式进行优化转移。(其实这里如果不使用滚动数组的话也可以,毕竟 i 那一维的大小已经被我们控制在了 O(n),而加上 j 那一维,空间复杂度为 O(nn) 也不会爆空间)

  • 对于编号 i>n 的物品:

由于此类物品无法取完,故不用考虑每种物品取了多少个。我们发现这类物品我们最多只能取 n 个,故不妨设 g(i,j) 为取 i 个此类物品,取出的这 i 个物品总体积为 j 的方案数。我们考虑如何列递推式,要想知道 g(i,j),就需要知道其可以由哪些状态变化而来,注意这里提到的状态必须保证计数不重(前驱状态和后续状态一一对应)不漏(不会少考虑情况) 。考虑到这两点后我们就来寻找这些状态吧 qwq

首先容易想到的是取 i 个物品的状态一定是可以由取 i1 个物品的状态推出来的,但这时我们似乎需要枚举 g(i1,(i1)×(n1))g(i1,jn1) 所有值,这显然还是会超时,但我们发现一个有趣的事情是,如果我们取了一个体积大于 n+1 的物品,我们可以将其看成先取了一个体积为 n+1 的物品,再通过某种方式使物品的总体积“增长”到我们要的目标 j,考虑寻找增长的方式,由于前后状态必须一一对应,所以每次所有已选的物品的体积增长量必须相同,由于不能跳过某些状态,所以这个增长量只能为 1,所以我们就找到了一种增长的方法,使得这样计数不重不漏。

所以可以推出 g(i,j) 的状态有两种:一种是已经取了 i1 个物品,又取了一个体积为 n+1 的物品,达到了状态 g(i,j),即 g(i1,jn1);一种是我们上文中所述的,将取体积大于 n+1 的物品的情况进行分解,将其分解为第一种状态与“增长”这两步,由于第一步在前面的状态中肯定会被当成第一种状态算进答案中,故我们只需考虑如何利用“增长”统计这部分答案,而正如上文所述,增长是要求每一个已选物品体积均增加 1,所以总体积增加 i,而已选物品数量是不会通过增长改变的,所以这一种可以推出 g(i,j) 的状态为 g(i,ji)

综上,状态转移方程为:

g(i,j)=g(i1,jn1)+g(i,ji)(g(0,0)=1)

由于我们将物品按编号分类讨论,所以我们最终要使用乘法原理合并答案,我们设 F(i)=f(n,i),G(i)=j=0ng(j,i),则答案为:

i=0nF(i)G(ni)

两部分时间复杂度均为 O(nn),故总时间复杂度也为 O(nn)

下面是简短的代码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;
}
posted @   Nickel_Angel  阅读(455)  评论(0编辑  收藏  举报
编辑推荐:
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示