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

题目

题目链接:http://www.51nod.com/Challenge/Problem.html#problemId=1597
你有一个大小为 \(n\) 的背包,你有 \(n\) 种物品,第 \(i\) 种物品的大小为 \(i\),且有 \(i\) 个,求装满这个背包的方案数有多少。答案对 23333333 取模。
两种方案不同当且仅当存在至少一个数 \(i\) 满足第 \(i\) 种物品使用的数量不同。
\(n\leq 10^5\)

思路

这题看上去就非常根号分治。考虑分别求出物品编号小于 \(\sqrt{n}\) 和大于等于 \(\sqrt{n}\) 时的背包,然后合并。
首先来看小于 \(\sqrt{n}\)。此时每个物品是有不能取超过 \(i\) 个的限制的。由于物品数只有 \(O(\sqrt{n})\),所以可以直接设 \(f[i][j]\) 表示前 \(i\) 个物品,使用了 \(j\) 的容量的方案数。
然后有转移

\[f[i][j]=\sum^{i}_{k=0}f[i-1][j-ki] \]

就是枚举用多少个物品 \(i\)。这个玩意前缀和优化一下就好了。
然后再看编号大于等于 \(\sqrt{n}\) 的物品,我么发现这些物品一定是取不到上限的,所以其实等价于一个完全背包。
有一个很经典的 trick:假设我们有一个序列 \(a\),我们可以通过如下操作构成这个序列:添加一个大小为 \(1\) 的物品,或者将所有物品大小加一。不难发现这个操作顺序与序列 \(a\) 是一一对应的。
同理,设 \(g[i][j]\) 表示选择了 \(i\) 个编号超过 \(\sqrt{n}\) 的物品,容量之和为 \(j\) 的方案数。那么考虑添加一个大小为 \(\sqrt{n}\) 的物品,或者将所有物品大小加一,有

\[g[i][j]=g[i][j-i]+g[i-1][j-\sqrt{n}] \]

最后合并背包即可。注意需要滚动数组。
时间复杂度 \(O(n\sqrt{n})\)

代码

#include <bits/stdc++.h>
using namespace std;

const int N=100010,M=320,MOD=23333333;
int n,ans,f[2][N],g[2][N],h[N];

int main()
{
	scanf("%d",&n);
	f[0][0]=g[0][0]=1;
	for (int i=1;i<M;i++)
	{
		int id=i&1;
		memset(f[id],0,sizeof(f[id]));
		for (int j=0;j<=n;j++)
		{
			h[j]=(f[id^1][j]+((j>=i) ? h[j-i] : 0))%MOD;
			f[id][j]=(h[j]-((j>=i*(i+1)) ? h[j-i*(i+1)] : 0))%MOD;
		}
	}		
	memset(h,0,sizeof(h));
	for (int i=1;i<=M;i++)
	{
		int id=i&1;
		memset(g[id],0,sizeof(g[id]));
		for (int j=0;j<=n;j++)
		{
			if (j>=i) g[id][j]=(g[id][j]+g[id][j-i])%MOD;
			if (j>=M) g[id][j]=(g[id][j]+g[id^1][j-M])%MOD;
			h[j]=(h[j]+g[id][j])%MOD;
		}
	}
	h[0]=1;
	for (int i=0;i<=n;i++)
		ans=(ans+1LL*f[1][i]*h[n-i])%MOD;
	printf("%d",(ans%MOD+MOD)%MOD);
	return 0;
}
posted @ 2021-04-25 15:05  stoorz  阅读(184)  评论(0编辑  收藏  举报