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

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

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

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

先来看体积小于 n n 的物体 设 f[i][j] f [ i ] [ j ] 表示在前 i i 个物品中装的体积为j的方案数, 那么可以很轻松地列出式子 f[i][j]=k<=if[i1][ji×k] f [ i ] [ j ] = ∑ k <= i f [ i − 1 ] [ j − i × k ] 那么我们记一个前缀和数组 tmp t m p ,每次把 f[i1][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 的体积都不能达到j的话,那么就不能转移过来,我们转移的时候判断一下去掉这部分就可以了。

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

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

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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值