给定 N 个正整数 A1,A2,…,AN,从中选出若干个数,使它们的和为 M,求有多少种选择方案。
输入格式
第一行包含两个整数 N 和 M。
第二行包含 N 个整数,表示 A1,A2,…,AN。
输出格式
包含一个整数,表示可选方案数。
数据范围
1≤N≤100,
1≤M≤10000,
1≤Ai≤1000,
输入样例:
4 4
1 1 2 2
输出样例:
3
求方案数问题对一部分人来说可能理解起来比较困难,实际上我也反复思考过很多次
我们先思考二维状态下的表示:
依据表格我们可以清晰的看到:第一次我们枚举数字1的时候可以组成的结果只能为1,其他的数字我们是组合不来的
而且我们需要注意的是第一次我们在枚举数字1的时候的状态是由0转移而来的,所以我们在初始化的时候就应该把f[0][0]设为1
我们在第二次枚举的时候发现也是数字1,因此数字1就有了两种表示,而由于我们之前已经有一个1了,两个1可以表示为2,所以数字2我们是可以组合而来的,因此数字2的种类数变成了1
同理数字2的状态转移是由数字1转移得到的f[2][2] += f[1][1](含义就是由于之前我们可以表示出数字1,而现在的枚举到的数字也是1,因此数字2的组合数就等于之前数字1的组合数)
所以状态转移方程我们很容易归纳得到是f[i][j] += f[i-1][j-num[i]](f[i][j]含义为前i个数中可以表示成j的方案数)
但是还有一点需要注意,我们在枚举下一行数据的时候新的一行的数据本来就是空值,所以我们要继承上一行的状态
是不可令m从num[i]开始的,这样会丢失数据,切记!
同时我们为了转移状态需要把f[0][0]初始化为1
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,m; //表示一共n个数,和为m
int a[1001];
int f[1001][1001];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
f[0][0] = 1;
for(int i=1;i<=n;i++)
for(int j=0;j<=m;j++)
{
f[i][j] = f[i-1][j];
if(j>=a[i])
f[i][j] += f[i-1][j-a[i]];
}
cout<<f[n][m];
}
接下来我们考虑如何优化
为什么可以优化?与01背包的原理一毛一样
同样的我们也需要逆序更新
//从中选任意多个数,使他们的和为m,求选择方案
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,m; //表示一共n个数,和为m
int a[1001];
int f[1001];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
f[0] = 1;
for(int i=1;i<=n;i++)
for(int j=m;j>=a[i];j--)
f[j] += f[j-a[i]];
printf("%d",f[m]);
return 0;
}
由于我们是在同一行上更新,所以我们在枚举的时候就已经继承了上一行的状态,我们看到代码精简了很多
要加油啊!!