[LOJ#6089] 小 Y 的背包计数问题 分块dp优化 校内模拟8-5 T3

题目链接
这个题细节有一点点多啊…. 不过反正是道很妙的题就对了
考场上的我只打出了暴力的普通背包

这道题是分块思想在背包上的运用

我们发现体积大于 n 的物品是取不完的,而且这部分物品最多选 n 个 考虑用完全背包来解决这部分问题
而体积小于等于 n 的物品只有 n 种 我们可以考虑用多重背包来解决这个问题
这样我们可以考虑在 O ( n n ) 的时间内解决这两个问题, 最后再用乘法原理合并答案就好啦

先来看体积小于 n 的物体 设 f [ i ] [ j ] 表示在前 i 个物品中装的体积为 j 的方案数, 那么可以很轻松地列出式子 f [ i ] [ j ] = k <= i f [ i 1 ] [ j i × k ] 那么我们记一个前缀和数组 t m p ,每次把 f [ i 1 ] [ j ] 累加到 t m p [ j % i ] 中, 但是这样可能会发现一个问题那就是如果 i × i + j % i < j ,意思就是上一个状态的体积加上放满 i 的体积都不能达到 j 的话,那么就不能转移过来,我们转移的时候判断一下去掉这部分就可以了。

然后再来看体积大于 n 的物体,设 F [ i ] [ j ] 表示装了 i 个物体体积为 j 的方案数,我们把这个问题抽象一下,变成我们要构造一个序列,每次可以对序列中所有的值加上 1 ,或者在序列末尾添上一个大小为 n + 1 的元素,我们来简单证明一下这个模型转化的正确性,首先这两个操作能够把所有的末状态都表示出来是很显然的,然后我们现在来考虑一下是否一个物品选择状态只对应一个序列,设其中的两个操作分别为 x y ,每一个物品选择肯定至少对应一个 x , y 序列,那么我们现在来证明他只对应一个序列,首先每个添加进来的元素最后的值为每个 y 操作后面的 x 操作数目 + n + 1 ,序列的长度一定为 y 操作的个数,如果两个不同的操作序列对应同一个物品选择,那么他们肯定 y 操作个数相同,并且至少存在一个 x 操作的位置不同,那么这个时候一定存在一个 y 操作后面的 x 操作的个数与前一种情况不同,那么就至少有一项不重复的情况,这个模型的正确性得证。

最后再用乘法原理合并两个问题即可,时间复杂度 O ( n n )

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

猜你喜欢

转载自blog.csdn.net/lunch__/article/details/81452694
今日推荐