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;
}