题目链接
这个题细节有一点点多啊…. 不过反正是道很妙的题就对了
考场上的我只打出了暴力的普通背包
这道题是分块思想在背包上的运用
我们发现体积大于
n−−√
n
的物品是取不完的,而且这部分物品最多选
n−−√
n
个 考虑用完全背包来解决这部分问题
而体积小于等于
n−−√
n
的物品只有
n−−√
n
种 我们可以考虑用多重背包来解决这个问题
这样我们可以考虑在
O(nn−−√)
O
(
n
n
)
的时间内解决这两个问题, 最后再用乘法原理合并答案就好啦
先来看体积小于 n−−√ n 的物体 设 f[i][j] f [ i ] [ j ] 表示在前 i i 个物品中装的体积为的方案数, 那么可以很轻松地列出式子 f[i][j]=∑k<=if[i−1][j−i×k] f [ i ] [ j ] = ∑ k <= i f [ i − 1 ] [ j − i × k ] 那么我们记一个前缀和数组 tmp t m p ,每次把 f[i−1][j] f [ i − 1 ] [ j ] 累加到 tmp[j%i] t m p [ j % i ] 中, 但是这样可能会发现一个问题那就是如果 i×i+j%i<j i × i + j % i < j ,意思就是上一个状态的体积加上放满 i i 的体积都不能达到的话,那么就不能转移过来,我们转移的时候判断一下去掉这部分就可以了。
然后再来看体积大于 n−−√ n 的物体,设 F[i][j] F [ i ] [ j ] 表示装了 i i 个物体体积为的方案数,我们把这个问题抽象一下,变成我们要构造一个序列,每次可以对序列中所有的值加上 1 1 ,或者在序列末尾添上一个大小为的元素,我们来简单证明一下这个模型转化的正确性,首先这两个操作能够把所有的末状态都表示出来是很显然的,然后我们现在来考虑一下是否一个物品选择状态只对应一个序列,设其中的两个操作分别为 x x 和,每一个物品选择肯定至少对应一个 x,y x , y 序列,那么我们现在来证明他只对应一个序列,首先每个添加进来的元素最后的值为每个 y y 操作后面的操作数目 + n−−√+1 n + 1 ,序列的长度一定为 y y 操作的个数,如果两个不同的操作序列对应同一个物品选择,那么他们肯定操作个数相同,并且至少存在一个 x x 操作的位置不同,那么这个时候一定存在一个操作后面的 x x 操作的个数与前一种情况不同,那么就至少有一项不重复的情况,这个模型的正确性得证。
最后再用乘法原理合并两个问题即可,时间复杂度
Codes
#include<bits/stdc++.h>
#define For(i, a, b) for(register int i = a; i <= b; ++ i)
using namespace std;
const int maxn = 350, maxm = 1e5 + 10, mod = 23333333;
int n, m, tmp[maxn], f[2][maxm], F[maxn][maxm];
void add(int &x, int y) {
x += y;
if(x >= mod)
x -= mod;
if(x < 0)
x += mod;
}
int main() {
#ifndef ONLINE_JUDGE
freopen("bag.in", "r", stdin);
freopen("bag.out", "w", stdout);
#endif
scanf("%d", &n);
m = sqrt(n);
f[0][0] = F[0][0] = 1;
For(i, 1, m) {
int now = (i & 1);
For(j, 0, n) {
add(tmp[j % i], f[now ^ 1][j]);
if(j >= i * (i + 1) + j % i)
add(tmp[j % i], -f[now ^ 1][j - i * (i + 1)]);
f[now][j] = tmp[j % i];
}
For(j, 0, i) tmp[j] = 0;
}
For(i, 1, m + 1)
For(j, 0, n) {
if(j + i <= n) add(F[i][j + i], F[i][j]);
if(j + m + 1 <= n) add(F[i][j + m + 1], F[i - 1][j]);
}
int ans = 0;
For(i, 0, m + 1)
For(j, 0, n)
add(ans, 1ll * f[m % 2][j] * F[i][n - j] % mod);
printf("%d\n", ans);
return 0;
}