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[i−1][j], choosef[i−1][j−w], 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;
}