(算法提高课)动态规划-背包问题2

278. 数字组合(01背包选满方案问题)

给定 N个正整数 A 1 , A 2 , … , A N A_1,A_2,…,A_N A1,A2,,AN,从中选出若干个数,使它们的和为 M,求有多少种选择方案。
输入格式
第一行包含两个整数 N和 M。
第二行包含 N个整数,表示 A 1 , A 2 , … , A N A_1,A_2,…,A_N A1,A2,,AN
输出格式
包含一个整数,表示可选方案数。
数据范围
1≤N≤100
1≤M≤10000
1≤Ai≤1000
答案保证在 int 范围内。
输入样例:
4 4
1 1 2 2
输出样例:
3

难点在状态转移方程中
此时 f [ i ] [ j ] f[i][j] f[i][j] 变为从前 i i i 件物品中取出重量恰好为 j j j 的方案数目是多少
f [ i ] [ j ] = { f [ i − 1 ] [ j ] ,    c h o o s e f [ i − 1 ] [ j − w ] ,    n o t   c h o o s e f[i][j] = \begin{cases} f[i - 1][j] ,~~choose \\ f[i - 1][j - w], ~~not~choose \end{cases} f[i][j]={f[i1][j],  choosef[i1][jw],  not choose

//理解版本(未简化版本),理解转化为01背包问题的思路和状态转移方程
#include<bits/stdc++.h>
using namespace std;
const int N = 110;
const int M = 10010;
int f[N][M];   //f[i][j]用来记录从前I个物品中选出总重量恰好为j的方案数目

int main()
{
    int n,m; cin>>n>>m;
    
    //初始化!!注意标准的01背包问题是有初始化的,也即将边缘全部初始化为0,只是开全局数组之后自动置零简化了这一步而已
    for(int i = 0;i <= n;i ++){
        f[i][0] = 1;    //前I个物品的总重量为0,选法方案唯一,即全不选
    }
    
    for(int i = 1;i <= n;i ++){
        int w; cin>>w;

        for(int j = 1;j <= m;j ++){
            //该物品不被选
            //等于在前i-1件物品中挑选总重为J的方案数
            f[i][j] = f[i - 1][j];  
            
            //该物品被选
            //等于在前I-1件物品中挑选总重为J-W的方案数(该数字一定被选择) + 本身已有的方案数
            //(最后是要统计和数)
            if(j >= w)
                f[i][j] += f[i - 1][j - w];
        }
    }
    cout<<f[n][m];
    return 0;
}

将dp数组简化为一维的滚动数组

//简化版本
#include<bits/stdc++.h>
using namespace std;
const int M = 10010;
int f[M];   //f[i]表示总重量恰好为i的时候,有多少种选择方案数
int main()
{
    int n,m; cin>>n>>m;
    f[0] = 1;   //初始化
    
    for(int i = 0;i < n;i ++){
        int w; cin>>w;
        for(int j = m;j >= w;j --){
            f[j] += f[j - w];     
            //解释:f[j] = max(f[j], f[j] + f[j - w]) = f[j] + f[j - w];(都为非0值)
        }
    }
    
    cout<<f[m];
    return 0;
}

1023. 买书(完全背包恰好选满模型)

小明手里有n元钱全部用来买书,书的价格为10元,20元,50元,100元。
问小明有多少种买书方案?(每种书可购买多本)
输入格式
一个整数 n,代表总共钱数。
输出格式
一个整数,代表选择方案种数。
数据范围
0≤n≤1000
输入样例1:
20
输出样例1:
2
输入样例2:
15
输出样例2:
0
输入样例3:
0
输出样例3:
1

这题和上题太像太像了,题设条件基本相同,不同的是书籍可以重复选择

//在上一题的版本上进阶了可重复选择的条件
#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
int f[N];
int w[] = {0,10,20,50,100};

int main()
{
    int n; cin>>n;
    f[0] = 1;
    
    for(int i = 1;i <= 4;i ++){
    //与上题结构一模一样,不同的是逆向循环改成正向循环(重点理解!!)
    //逆向循环是为了保留上层数据建立滚动数组,而在此题环境下正确答案不完全基于上层数据,而且根据已经选择的书籍和总重量实时更新
    //此时前面的更新将影响后面(因为可以重复更新!!!当背包容量变大,且多出的重量刚好为整数倍的时候,就可以重复放入当前物体)
        for(int j = w[i];j <= n;j ++){
            f[j] += f[j - w[i]];
        }
    }
    cout<<f[n];
    return 0;
}

朴素版本,比起上一题多加了一层循环(因为当前物品可不止被选择一次)

#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
int f[5][N];
int w[] = {0,10,20,50,100};

int main()
{
    int n; cin>>n;
    //这里反而不能将所有行的第一列初始为1
    //for(int i = 0;i <= 4;i ++)
        //f[i][0] = 1;
    f[0][0] = 1;
    
    for(int i = 1;i <= 4;i ++){
        for(int j = 0;j <= n;j ++){
            for(int k = 0;k * w[i] <= j;k ++){
                f[i][j] += f[i - 1][j - k * w[i]];   //这里直接做+=处理,如果前面第一列全部初始化为1,则下一行的+=操作会使得每行首列越加越多,不符合题意
            }
        }
    }
    cout<<f[4][n];
    /*
    写法也可以是
    for(int i = 0;i <= 4;i ++)
        f[i][0] = 1;
    
    for(int i = 1;i <= 4;i ++){
        for(int j = 0;j <= n;j ++){
            f[i][j] = f[i - 1][j];   //直接的等于处理不会使得首列越来越多
            if(j >= w[i])
                for(int k = 1;k * w[i] <= j;k ++){
                    f[i][j] += f[i - 1][j - k * w[i]];
                }
        }
    }
    */
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值