[51nod1597]有限背包计数问题
试题描述
输入
第一行一个正整数n
1<=n<=10^5
输出
输入示例
3
输出示例
2
数据规模及约定
见“输入”
题解
分块的形式真是多种多样,这题就是一个分块 dp。
这里分块的意思是对 1~i 这 n 种物品分类讨论。即对体积小于等于 sqrt(n) 的部分使用一种 dp 方法解决,对于体积大于 sqrt(n) 的部分使用另一种 dp 方法解决,最后由于“小于等于 sqrt(n) 和大于 sqrt(n) 的部分”没有交集,相互独立,可以使用乘法原理进行合并。
首先考虑只选用体积小于等于 sqrt(n) 的物品放入背包,这是一个多重背包问题,设 f(i, j) 表示考虑前 i 种物品组成体积 j 的方案数,由于这题特殊性(体积为 i 的物品有 i 个),我们可以对 j mod i 将 f(i, j) 分类(共有 i 类),然后对于 j mod i = k 的类别计算一下 f(i, j) 前缀和 sum[j](共有 [n / i] 个前缀和),更新 f(i+1, j) 的时候就用 sum[j] - sum[j-i*i] 就好了(记得判断 j - i * i 会不会越界)
然后考虑体积大于 sqrt(n) 的物品,这个时候可以不考虑个数限制,因为每个物品不会选择超过 sqrt(n) 个。考虑另一种 dp,g(i, j) 表示选择了 i 个(注意是“个”不是“种”,显然 i ≤ sqrt(n))物品,组成体积 j 的方案数。这个 dp 中我们只关心体积最小的物品,有两种转移:一,放入一个新的体积最小的物品(体积为 sqrt(n) + 1);二,所有物品体积 +1。
注意:两种 dp 都需要开滚动数组。
最后用乘法原理乘起来,累加,这题就解决了。
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <cmath>
using namespace std;
int read() {
int x = 0, f = 1; char c = getchar();
while(!isdigit(c)){ if(x == '-') f = -1; c = getchar(); }
while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
return x * f;
}
#define maxn 100010
#define MOD 23333333
#define LL long long
int n, f[maxn], g[2][maxn], gsum[maxn];
int main() {
n = read();
int m = (int)sqrt(n + .5);
f[0] = 1;
for(int i = 1; i <= m; i++)
for(int mod = 0; mod < i; mod++) {
int sum = 0, j, cnt = 0;
for(j = mod; j <= n; j += i) {
sum += f[j];
if(sum >= MOD) sum -= MOD;
if(++cnt > i) {
sum -= f[j-i*i];
if(sum < 0) sum += MOD;
cnt--;
}
}
for(j -= i; j >= mod; j -= i) {
sum -= f[j];
if(sum < 0) sum += MOD;
if(j >= i * i) {
sum += f[j-i*i];
if(sum >= MOD) sum -= MOD;
}
f[j] += sum;
if(f[j] >= MOD) f[j] -= MOD;
}
}
// for(int i = 0; i <= n; i++) printf("%d%c", f[i], i < n ? ' ' : '\n');
int curg = 0;
g[0][0] = 1;
for(int i = 0; i <= m; i++, curg ^= 1) {
memset(g[curg^1], 0, sizeof(g[curg^1]));
for(int j = 0; j <= n; j++) if(g[curg][j]) {
// printf("g %d %d: %d\n", i, j, g[curg][j]);
gsum[j] += g[curg][j];
if(gsum[j] >= MOD) gsum[j] -= MOD;
if(i < m && j + m + 1 <= n) {
g[curg^1][j+m+1] += g[curg][j];
if(g[curg^1][j+m+1] >= MOD) g[curg^1][j+m+1] -= MOD;
}
if(i && j + i <= n) {
g[curg][j+i] += g[curg][j];
if(g[curg][j+i] >= MOD) g[curg][j+i] -= MOD;
}
}
}
curg ^= 1;
int ans = 0;
for(int i = 0; i <= n; i++) {
ans += (LL)f[i] * gsum[n-i] % MOD;
if(ans >= MOD) ans -= MOD;
}
printf("%d\n", ans);
return 0;
}