试题描述
你有一个大小为n的背包,你有n种物品,第i种物品的大小为i,且有i个,求装满这个背包的方案数有多少
两种方案不同当且仅当存在至少一个数i满足第i种物品使用的数量不同
输入
第一行一个正整数n
1<=n<=10^5
输出
一个非负整数表示答案,你需要将答案对23333333取模
输入示例
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; }