【LOJ6089】小 Y 的背包计数问题(背包+阈值)
大致题意: 有一个大小为\(n\)的背包和\(n\)种物品,其中第\(i\)种物品体积为\(i\)、数量为\(i\)。求把背包恰好装满的方案数。
阈值
法老讲课题,一道非常有技巧也非常有启发性的题目。
这个问题直接去看就是一个多重背包,但实际上我们考虑,令\(S=\sqrt n\),则体积大于\(S\)的物品肯定无法全部放入背包中,可以看作有无限个。
也就是说,我们可以对体积小于等于\(S\)的物品做多重背包,对体积大于\(S\)的物品做完全背包。
最后的合并答案实际上只要\(O(n)\)扫一遍就可以了。
多重背包
众所周知,多重背包计数问题可以转化为完全背包。
只要先跑一遍完全背包,然后倒序枚举\(j\),令\(f_{i,j}\)减去不合法答案\(f_{i,j-(i+1)\times i}\),就可以了。
由于只有\(\sqrt n\)个物品体积小于等于\(S\),复杂度为\(O(n\sqrt n)\)。
完全背包
考虑体积大于\(S\)的物品个数仍旧是\(O(n)\)的,暴力完全背包复杂度依旧是\(O(n^2)\)。
这里就涉及一个非常巧妙的优化。
我们假设当前选了\(i\)个物品,体积总和为\(j\),接下来我们有两种操作方式:
- 新增一个物品,体积为\(S+1\)。
- 给当前所有的物品体积加\(1\)。
不难发现,所有的方案都可以通过这两种操作方式唯一得到。
也就是说,每次枚举\(i,j\),令\(g_{i+1,j+S+1}\)和\(g_{i,j+i}\)分别加上\(g_{i,j}\)。
由于最多只能选\(S\)个物品,复杂度为\(O(n\sqrt n)\)。
代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define SN 320
#define X 23333333
#define Inc(x,y) ((x+=(y))>=X&&(x-=X))
using namespace std;
int n,s;
int f[SN+5][N+5];I void DP1()//体积小于等于S,多重背包
{
RI i,j;for(f[0][0]=i=1;i<=s;++i)
{
for(j=0;j<=n;++j) f[i][j]=(f[i-1][j]+(j>=i?f[i][j-i]:0))%X;//完全背包
for(j=n;j>=(i+1)*i;--j) Inc(f[i][j],X-f[i][j-(i+1)*i]);//减去不合法答案
}
}
int g[SN+5][N+5],tot[N+5];I void DP2()//体积大于S,完全背包
{
RI i,j;for(g[0][0]=1,i=0;i<=s;++i) for(j=0;j<=n;++j) Inc(tot[j],g[i][j]),//tot统计答案
j+s+1<=n&&Inc(g[i+1][j+s+1],g[i][j]),j+i<=n&&Inc(g[i][j+i],g[i][j]);//两种转移
}
int main()
{
RI i,t=0;for(scanf("%d",&n),s=sqrt(n),DP1(),DP2(),i=0;i<=n;++i) t=(1LL*f[s][i]*tot[n-i]+t)%X;//扫一遍求答案
return printf("%d\n",t),0;//输出答案
}
待到再迷茫时回头望,所有脚印会发出光芒