51nod 有限背包计数问题

这道题卡空间。

思路:

当物品编号 \(> \sqrt n\)时,那么每一个物品都不会被用完。可以用完全背包计数,但是复杂度太高。我们可以这样设计DP状态 ,f[i][j]表示用了i个物品,凑出体积为j的方案数,类似于NOIP2011数的划分。我们按照最小的数进行状态转移,分为两种情况。
1.最小数是 \(\sqrt n + 1\) ,此方案数为\(f[i - 1][j - (\sqrt n + 1)]\)
2.最小数大于 \(\sqrt n + 1\), 这个状态可以由 \(f[i][j-i]\)得到,就相当于将每一个数减去1,就变成了用i个物品凑出体积为j-i的方案数。

当物品编号\(\leq \sqrt n\)时,可以直接用多重背包计数,需要前缀和优化。

/*
233

1167892
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>

using namespace std;

const int N = 1e5 + 100, mod = 23333333;
typedef long long LL;

int f[2][N], g[320][N];
//物品i有v[i]个,
int main()
{
	int n ;
	scanf("%d", &n);
	int m = sqrt(n);
	g[0][0] = 1;
	f[0][0] = 1;
	for(int i = 1; i <= m; i ++) {
		for(int j = 0; j <= n; j ++) {
			g[i][j] = f[i - 1 & 1][j] % mod;
			if(j >= i)
				g[i][j] = (g[i][j] + g[i][j - i]) % mod; 
			//前缀和优化DP ,相当于隔v[i]个数的前缀和 
			if( j >= (i + 1) * i)
				f[i & 1][j] = ((g[i][j] - g[i][j - (i + 1) * i]) % mod + mod) % mod;//前缀和思想 
			else
				f[i & 1][j] = g[i][j] % mod;
		}
	}//注意取模 
	memset(g, 0, sizeof(g));
	//处理 > sqrt(n)的物品 
	g[0][0] = 1;
	for(int i = 1; i <= m; i ++) {//将体积j划分为i份 ,并且划分的每一个数大于m 
		for(int j = 1; j <= n; j ++) {
			if(j >= m + 1)
				g[i][j] = g[i - 1][j - (m + 1)] % mod;
			if(j >= i)
				g[i][j] = (g[i][j] + g[i][j - i]) % mod;
		}
	}
	LL ans = 0;
	for(int i = 0; i <= n; i ++) {
		for(int j = 0; j <= m; j ++) {
			ans = (ans + (LL)f[m & 1][i] * g[j][n - i]) % mod;
		//	if( ans < 0)
		//	printf("%lld %lld  %lld\n", ans, f[m & 1][i], g[j][n - i]);
		}	
		
	}
	printf("%lld\n", ans);
	return 0;
}
posted @ 2020-10-22 10:28  王雨阳  阅读(126)  评论(0编辑  收藏  举报