51nod-有限背包计数问题【dp】

正题

题目链接:http://www.51nod.com/Challenge/Problem.html#problemId=1597


题目大意

n n n种物品,第 i i i个大小为 i i i且有 i i i个。

求恰好填满大小为 n n n的背包的方案数


解题思路

我们可以将背包分为两份,对于大小小于等于 n \sqrt n n 的物品,这样的物品数量不超过 n n n\sqrt n nn 个,所以我们可以用多重背包来做,这里要优化,我们将 f i , j f_{i,j} fi,j中根据 j % i j\%i j%i的不同来进行分组,这样只有组内可以相互转移且是在一个区间内转移,可以 O ( n n ) O(n\sqrt n) O(nn )的计算出答案,然后反向枚举压缩掉 i i i这一维就有方程(u枚举余数
f u + p ∗ i = ∑ p − i ≤ k ≤ p − 1 f u + k ∗ i f_{u+p*i}=\sum_{p-i\leq k\leq p-1}f_{u+k*i} fu+pi=pikp1fu+ki

然后对于大于 n \sqrt n n 的物品,我们可以将其视为有无限个物品,所以我们可以将问题变为求将一个数字分解成若干个可以相同但是要大于 n \sqrt n n 的数字的和的方案数。

考虑一个集合,每次可以加入一个数字(物品) n + 1 \sqrt n+1 n +1或者全体数字都 + 1 +1 +1(物品都变大一个)。

然后设 g i , j g_{i,j} gi,j表示目前集合内数字和(选择的物品和)为 i i i,然后集合内数字(物品)个数为 j j j,就有方程 g i , j = g i − n − 1 , j + g i , j − 1 g_{i,j}=g_{i-\sqrt n-1,j}+g_{i,j-1} gi,j=gin 1,j+gi,j1

然后转移即可


c o d e code code

#include
#include
#include
#include
using namespace std;
const int N=1e5+10,XJQ=23333333;
int n,T,f[N],g[2][N],s[N],ans;
int main()
{
	scanf("%d",&n);T=sqrt(n)+1;
	f[0]=1;
	for(int i=1;i<T;i++){
		for(int u=0;u<i;u++){
			int sum=0,t=(n-u)/i;
			for(int p=t;p>max(t-i,-1);p--)
				(sum+=f[u+i*p])%=XJQ; 
			for(int p=t;p>=0;p--){
				if(p-i>=0)sum=(sum+f[u+i*(p-i)])%XJQ;
				sum=(sum-f[u+i*p]+XJQ)%XJQ;
				f[u+p*i]=(f[u+p*i]+sum)%XJQ;
			}
		}
	}
	g[0][0]=s[0]=1;
	for(int i=1;i<T;i++){
		memset(g[i&1],0,sizeof(g[i&1]));
		for(int j=T;j<=n;j++)
			g[i&1][j]=(g[~i&1][j-T]+g[i&1][j-i])%XJQ,
			(s[j]+=g[i&1][j])%=XJQ;
	}
	for(int i=0;i<=n;i++)
		(ans+=1ll*f[i]*s[n-i]%XJQ)%=XJQ;
	printf("%d",ans);
}

你可能感兴趣的:(dp,51nod,dp)