AcWing
  • 首页
  • 课程
  • 题库
  • 更多
    • 竞赛
    • 题解
    • 分享
    • 问答
    • 应用
    • 校园
  • 关闭
    历史记录
    清除记录
    猜你想搜
    AcWing热点
  • App
  • 登录/注册

AcWing 1023. 买书【完全背包求解方案数+朴素优化】    原题链接    简单

作者: 作者的头像   一只野生彩色铅笔 ,  2021-06-11 20:04:04 ,  所有人可见 ,  阅读 5996


67


22

最近在补全提高课所有题目的题解,宣传一下汇总的地方提高课题解补全计划

题目描述

小明有 $m$ 块钱,现有 $10$ 元, $20$ 元, $50$ 元, $100$ 元 的书

每本书可以购买多次,求小明有多少种买书方案

分析

一共有 $n$ 个物品,每个物品有体积 $v_i$,价值 $w_i$,每个物品能够选多次

求总体积不超过 $m$ 的方案数

这是一道裸的 完全背包问题 求解方案数

废话不多说,直接上 闫氏DP分析法

闫氏DP分析法

初始状态:f[0][0]

目标状态:f[n][m]

IMG_8A6C4D001CEB-1.jpeg

Code(朴素版)

最坏时间复杂度:$O(n^2\times m)$

#include <iostream>

using namespace std;

const int N = 5, M = 1010;

int n = 4, m;
int v[N] = {0, 10, 20, 50, 100};
int f[N][M];

int main()
{
    //input
    cin >> m;

    //dp
    f[0][0] = 1;
    for (int i = 1; i <= n; ++ i)
    {
        for (int j = 0; j <= m; ++ j)
        {
            for (int k = 0; v[i] * k <= j; ++ k)
            {
                f[i][j] += f[i - 1][j - v[i] * k];
            }
        }
    }

    //output
    cout << f[n][m] << endl;

    return 0;
}

完全背包—经典优化

观察 $f(i,j)$ 的 状态转移方程 进行变形

$$ \begin{matrix} f(i, j) &=& f(i-1,j) &+& f(i-1,j-v_i) &+& \cdots &+& f(i-1,j-sv_i) \\\ f(i, j-v_i) &= &&&f(i-1, j-v_i) &+& \cdots &+& f(i-1,j-sv_i) \end{matrix} $$

由上述两个等式可以获得如下递推式:

$$ f(i,j) = f(i-1,j) + f(i, j-v_i) $$

把这个等式作为 状态转移方程 ,就可以把时间复杂度优化到 $O(n\times m)$

同时,观察到该 转移方程 对于第 i 阶段的状态,只会使用第 i-1 层和第 i 层的状态

因此我们也可以采用 01背包 的 空间优化方案

时间复杂度:$O(n\times m)$

空间复杂度:$O(m)$

#include <iostream>

using namespace std;

const int N = 5, M = 1010;

int n = 4, m;
int v[N] = {0, 10, 20, 50, 100};
int f[M];

int main()
{
    //input
    cin >> m;

    //dp
    f[0] = 1;
    for (int i = 1; i <= n; ++ i)
    {
        for (int j = v[i]; j <= m; ++ j)
        {
            f[j] += f[j - v[i]];
        }
    }

    //output
    cout << f[m] << endl;

    return 0;
}

46 评论


用户头像
团子团子   2023-11-23 17:10      2    踩      回复

不是不超过,是体积正好为m的方案数

用户头像
cai_xing   2023-11-27 20:54         踩      回复

我也栽在这里了呜呜呜


用户头像
吃饱了就来刷题   2023-09-21 21:35      2    踩      回复

那个朴素版本,为什么01背包问题中的朴素版本需要将所有物品的f[i][0]都初始化为1,这里只需要初始化f[0][0]且初始其他行的f[i][0]会出错呢反而,不是应该前i件物品放入总重为0的背包中的方案还是只有1嘛

用户头像
西雪蓮Official   2023-10-16 17:12         踩      回复

大佬请问解决了吗?我也想问这个问题

用户头像
吃饱了就来刷题   2023-10-16 21:37    回复了 西雪蓮Official 的评论         踩      回复

我当时写的时候是觉得这条代码:$f[i][j] += f[i - 1][j - v[i] * k];$,如果将首列全部置为1的话,我直接做加等于操作会导致第一列的值越来越大,要不就设置一个限制条件,当j > w[i]的时候做这个操作,小于的时候直接等于,这样我试过也是正确的好像

用户头像
西雪蓮Official   2023-10-18 16:43    回复了 吃饱了就来刷题 的评论         踩      回复

我觉得一维优化后的令f[0] = 1和二维时的令f[0~n][0] = 1是等价的。一维滚动数组一直把f[0] =1这个值保留下去,也就相当于遍历到任意i的时候都有f[0] = 1,那不就是等价于令f[0~n][0] = 1吗?所以我感到疑惑,觉得这里优化前和优化后的代码是矛盾的。。。

用户头像
rookie.   2024-03-30 17:00         踩      回复

搞明白了吗?我也遇见这个问题了

用户头像
rookie.   2024-03-30 17:00         踩      回复

搞明白了吗?我也遇见这个问题了

用户头像
Azun.   2024-04-03 14:00    回复了 西雪蓮Official 的评论         踩      回复

不等价,二维的那个 f[1~n][0] 都会加上上一个的 f[(1~n)-1][0] ,因此f[1~n][0]都会是 1 , 相当于是迭代过程中给他初始化了。提前令f[0~n][0] = 1的话反而达不到预设的效果。。。

用户头像
Azun.   2024-04-03 14:00    回复了 rookie. 的评论         踩      回复

再想想

用户头像
MuQYY   2024-07-14 21:55    回复了 西雪蓮Official 的评论         踩      回复

我认为是这样的: f[0~n][0] = 1实际上是同一个状态。而你状态转移的时候用的加,如果提前使得f[1~n][0] = 1实际上是重复计算了这一个状态

用户头像
Hangya   2024-08-18 17:45         踩      回复

因为 循环里处理了其他f[i][0]的情况,即f[2][0] += f[1][0],此时f[2][0]就是1了(相当于初始化)。如果你前面再初始化的话就重复了


用户头像
Dionysus.   2022-04-05 10:41      2    踩      回复

请问那个优化的方程最后不是那个方程后面多了一项嘛

用户头像
糖豆   2023-05-10 11:42         踩      回复

不会多的,因为s值是一样的,原因:j这么大的空间中,最多能装下几个vi?当然数量是固定的,设为s,所以两个式子中的s是一样的,最后一项就是一样的。

用户头像
哈哈哈1234   2023-12-08 10:51         踩      回复

多重背包会多一项吧,完全背包不会多


用户头像
武破立法   2023-01-18 11:23      1    踩      回复

这个dp递推式优化好强啊。


用户头像
小张_2   2024-04-26 14:33         踩      回复
for(int i = 0; i < 4; i ++ )
for(int j = 0; j <= n; j += 10)
{
if(j >= v[i]) f[j] += f[j - v[i]];
}

因为面值最小的10的,所以其实在第二个循环的时候以10为单位递增可以更快一些(PS:只是因为所有面值都是10的倍数,因此不具有普适性)


用户头像
辞寒   2024-01-14 21:01         踩      回复

为啥f(i,j − v) 比 f(i, j) 少了一项,根据f(i, j) 表示的具体含义,不应该是一样的项数吗

用户头像
Boder_q   2024-01-31 03:10         踩      回复

假设前一个方程最后是 (i , j-svi ) 这里表示 v[i]最多选 s个,如果多选一个j-(s+1)vi 必然< 0 ,显然超过了总价钱或体积,所以后一个方程比前一个少了第
一项

用户头像
辞寒   2024-01-31 16:19    回复了 Boder_q 的评论         踩      回复

f(i, j) 的含义是前i个物品中选,总体积不超过j ,f[i][j] = f[i - 1][j] + f[i - 1][j - v] + ... + f[i - 1][j - sv] ,然后f[i][j - v] = f[i - 1][j - v] + f[i - 1][j - 2v] + ... + f[i - 1][j - kv] ,而且k = s - 1 ,推不出来f[i][j] = f[i - 1][j] + f[i][j - v] ,不应该是f[i][j] = f[i - 1][j] + f[i][j - v] + f[i - 1][j - sv] 吗

用户头像
辞寒   2024-01-31 16:20    回复了 辞寒 的评论         踩      回复

要如何证明f[i - 1][j - sv] = 0

用户头像
Boder_q   2024-01-31 16:26    回复了 辞寒 的评论         踩      回复

这个是看的 第 i的物品 最多是 j-sv 第二个方程最后也应该是 j-sv 啊 因为是同一个物品的体积是一样的

用户头像
辞寒   2024-01-31 16:39    回复了 Boder_q 的评论         踩      回复

f[i][j - v] 的含义是第i 个物品没错,但是j - v 的含义不是体积不超过j - v 吗,正式因为同一个物品,所以在相同单位体积下,最终能选的数量自然就少1了啊,所以才会有k = s - 1


用户头像
weige_9   2023-09-28 14:46         踩      回复

朴素版本的初始化能否用:n本书,0元钱,对应1种方案?
代码for(int i =0 ; i <= 4; i ++) f[i][0] = 1;
你是f[0][0] = 1;


用户头像
吴下阿蒙_吕子明   2023-08-03 19:12         踩      回复

Orz 好题解


用户头像
世蒂   2023-03-16 13:01         踩      回复

彩铅佬我的超人呜呜呜!


用户头像
ToThink   2023-03-15 15:45         踩      回复

借个图,谢谢大佬 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%


用户头像
武破立法   2023-01-18 11:23         踩      回复

这个dp递推式优化好强啊。


用户头像
武破立法   2023-01-18 11:23         踩      回复

这个dp递推式优化好强啊。


用户头像
武破立法   2023-01-18 11:23         踩      回复

这个dp递推式优化好强啊。


用户头像
Kingna   2022-08-11 20:40         踩      回复

大佬您的字迹怎么就这么好看呢?


用户头像
acmdyh   2022-02-28 11:58         踩      回复

恰好等于=,不用f[0]=1,f[i]=0x3f3f3f3f3f吗

用户头像
qbz666   2022-03-31 10:10         踩      回复

体积恰好求方案数 =1,方案数都不能初始化正无穷

用户头像
大伟老师的亲传弟子   2022-08-09 19:53    回复了 qbz666 的评论         踩      回复

背包这里关于恰好的问题,y总有过详细讲解吗?

用户头像
qbz666   2022-08-09 20:42    回复了 大伟老师的亲传弟子 的评论         踩      回复

小呆呆巨巨写过一篇dp初始化的探究,可以了解一下。

用户头像
大伟老师的亲传弟子   2022-08-09 22:05    回复了 qbz666 的评论         踩      回复

哈哈,能仙人指路一下吗?

用户头像
qbz666   2022-08-09 23:20    回复了 大伟老师的亲传弟子 的评论         踩      回复

https://blog.csdn.net/qq_52765554/article/details/123157907?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522166005842816782395365462%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=166005842816782395365462&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-3-123157907-null-null.nonecase&utm_term=%E8%83%8C%E5%8C%85%E9%97%AE%E9%A2%98&spm=1018.2226.3001.4450


用户头像
Critic   2022-02-12 09:26         踩      回复

请问为什么f[0] = 1呢

用户头像
逐风者   2022-02-17 15:58         踩      回复

因为当前的状态是由上一个状态转移过来的,f[0]也就是花费为0的选法有多少种,很显然什么都不能选,所以f[0]的选法只有一种,之后的所有状态都是由f[0]转移过来的。

用户头像
Critic   2022-02-17 19:38    回复了 逐风者 的评论         踩      回复

非常感谢~


用户头像
种花家的小兔子   2021-11-09 10:12         踩      回复

巨巨 有个问题, 假设n元钱不全部用完,这道题怎么写。。🙄

用户头像
一只野生彩色铅笔   2021-11-09 12:09         踩      回复

模型是背包问题最优方案数,要做两次DP,提高课后面有这道题我记得

用户头像
种花家的小兔子   2021-11-09 15:01    回复了 一只野生彩色铅笔 的评论         踩      回复

巨巨 可能理解错了, 上午想了想,如果不把钱用完,那么只需要改变集合的含义和初始化即可。
f[i]表示和<=i的方案数的集合

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <vector>
#include <map>
#include <set>
#include <queue>
#include <deque>
#include <stack>
#include <unordered_map>

#define IOS ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int maxn = 1e5 + 10;
typedef pair <int, int> PII;
int v[10];
int f[1010];
int main()
{
    int n;
    scanf("%d", &n);
    v[1] = 10, v[2] = 20, v[3] = 50, v[4] = 100;
    for(int i = 0 ; i <= n; i ++)
    f[i] = 1;
    for(int i = 1 ; i <= 4 ; i ++)
    {
        for(int j = v[i] ; j <= n ; j ++)
        {
//          f[i][j] = f[i - 1][j];
            f[j] = f[j];
//          if(j - v[i] >= 0)
            f[j] += f[j - v[i]];
//          f[j] += f[j - v[i]];
        }
    }   
    printf("%d\n", f[n] - f[n - 1]);
    return 0;
}
用户头像
一只野生彩色铅笔   2021-11-09 16:31    回复了 种花家的小兔子 的评论         踩      回复

确实 (𖦹‎.𖦹‎) 这题是直接把方案数作为状态属性的,你是对的

用户头像
又菜了   2022-02-08 16:04         踩      回复

小于等于n的所有方案数是不是可以在这个dp的后面求一遍前缀和得出s[n]呢.😢

用户头像
selfknow   2022-02-22 23:18    回复了 种花家的小兔子 的评论         踩      回复

就相当于原来只能从f1开始转移,现在变成了可以从f1,f2,f3,f4开始转移,感觉类似于二进制,四个数构成了全部的状态


App 内打开
你确定删除吗?
1024
x

© 2018-2025 AcWing 版权所有  |  京ICP备2021015969号-2
用户协议  |  隐私政策  |  常见问题  |  联系我们
AcWing
请输入登录信息
更多登录方式: 微信图标 qq图标 qq图标
请输入绑定的邮箱地址
请输入注册信息