【LOJ6089】小 Y 的背包计数问题(背包+阈值)

点此看题面

大致题意: 有一个大小为(n)的背包和(n)种物品,其中第(i)种物品体积为(i)、数量为(i)。求把背包恰好装满的方案数。

阈值

法老讲课题,一道非常有技巧也非常有启发性的题目。

这个问题直接去看就是一个多重背包,但实际上我们考虑,令(S=sqrt n),则体积大于(S)的物品肯定无法全部放入背包中,可以看作有无限个。

也就是说,我们可以对体积小于等于(S)的物品做多重背包,对体积大于(S)的物品做完全背包。

最后的合并答案实际上只要(O(n))扫一遍就可以了。

多重背包

众所周知,多重背包计数问题可以转化为完全背包。

只要先跑一遍完全背包,然后倒序枚举(j),令(f_{i,j})减去不合法答案(f_{i,j-(i+1) imes i}),就可以了。

由于只有(sqrt n)个物品体积小于等于(S),复杂度为(O(nsqrt 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(nsqrt 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
",t),0;//输出答案
}
原文地址:https://www.cnblogs.com/chenxiaoran666/p/LOJ6089.html