把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【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;//输出答案
}
posted @ 2020-07-21 07:21  TheLostWeak  阅读(305)  评论(0编辑  收藏  举报