首页 > 技术文章 > 51Nod-1597 有限背包计数问题 根号分治

hznumqf 2021-09-12 21:24 原文

51Nod-1597 有限背包计数问题 根号分治

题意

有一个大小为\(n\)的背包,第\(i\)种物品的大小是\(i\),且有\(i\)

求装满背包的方案数是多少个

\[1 \leq n \leq 1e5 \]

分析

容易发现当\(i > \sqrt{n}\)的时候个数大小相当于没有限制,因此这道题可以往根号分治的角度去思考

  • \(i \leq \sqrt{n}\)

考虑直接\(DP\)

\[dp[i][j] = \sum dp[i-1][j-k*i] \]

这个东西可以通过维护$ % i$ 的前缀和\(O(1)\)转移,复杂度\(O(n\sqrt{n})\)

  • \(i > \sqrt{n}\)

个数没有限制。

这里比较巧妙:把原问题转化为每次进行如下操作

1.加入一个最小的物品(大小为\(\sqrt{n} + 1\))

2.所以物品大小+1

容易证明这样的操作和原方案数是一一对应的

这样也可以简单的\(DP\)来实现

最后只需要分别统计一下即可

代码

const int maxn = 1e5 + 5;

int f1[2][maxn],f2[2][maxn];


int main(){
	int n = rd();
	int m = sqrt(n);
	f1[0][0] = 1;
	for(int i = 1;i <= m;i++){
		VI sum(i);
		for(int j = 0;j <= n;j++){
			add(sum[j % i],f1[i & 1 ^ 1][j]);
			if(j - i * (i + 1) >= j % i) add(sum[j % i],MOD - f1[i & 1 ^ 1][j - i  * (i + 1)]);
			f1[i & 1][j] = sum[j % i];
		}
	}
	VI sum(n + 1);
	sum[0] = 1;
	f2[0][0] = 1;
	for(int i = 1;i <= m;i++){
		memset(f2[i & 1] + (i - 1) * (m + 1),0,(m + 1) << 2);
		for(int j = i * (m + 1);j <= n;j++){
			f2[i & 1][j] = (f2[i & 1][j - i] + f2[i & 1 ^ 1][j - (m + 1)]) % MOD;
			add(sum[j],f2[i & 1][j]);
		}
	}
	int ans = 0;
	for(int i = 0;i <= n;i++)
		add(ans,mul(f1[m & 1][i],sum[n - i]));
	printf("%d",ans);
}

推荐阅读