首先,我们可以先清楚
这类题目给出的是一个数能否有若干个数构成
存在可以重复取,或者不可以重复取两种情况
类似如下这样的题
1.给定N个正整数A1,A2,…,AN,从中选出若干个数,使它们的和为M,求有多少种选择方案。
2.给定一个自然数N,要求把N拆分成若干个正整数相加的形式,参与加法运算的数可以重复。
求拆分的方案数 mod 2147483648的结果。
首先我们可以先确定DP[i]的状态都是固定的,都是为在数值为i的情况下,能取的情况有多少种
容易得知dp[0] = 1 作为初态
那么易得dp[i] += dp[i - arr[x]]
那么01背包的取法是从大到小取,即第二层循环是for(int i = m; i >= arr[j]; i --)
这样的话 会保证每个值只取一次
完全背包是从小到大取,即第二层循环是for(int i = arr[j]; i <= m; i ++)
这样的话 可以多取
还有一个关于dp状态二维的转移能否互相调换
是不可以互相调换的,因为调换后会存在重复方案的转移,比如1 2 2 跟 2 2 1
是一样的,但是调换for的顺序后,会导致状态的重叠
为什么说dp从大到小不会重复取呢?
因为大的先转移,避免了小的已经转移过了,然后再来转移大的,所以不会出现一个值重复取的情况
如果是小的先转移,在转移大的,会存在重复取的情况的转移
所以说,从大到小是01背包,从小到大是完全背包
附上01背包取数字的代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int n, m;
const int MAXN = 105;
LL dp[MAXN * 100];
LL arr[MAXN];
int main(){
scanf("%d%d", &n, &m);
for(int i = 0; i < n; i ++){
scanf("%lld", &arr[i]);
}
sort(arr, arr + n);
dp[0] = 1;
for(int i = 0; i < n; i ++){
for(int j = m; j >= arr[i]; j --){
dp[j] += dp[j - arr[i]];
}
}
printf("%lld
", dp[m]);
return 0;
}
附上完全背包:
#include <bits/stdc++.h>
using namespace std;
int n;
const int MAXN = 4005;
typedef long long LL;
const LL mod = 2147483648;
LL dp[MAXN];
int main(){
ios::sync_with_stdio(false);
cin >> n;
dp[0] = 1;
for(int i = 1; i <= n; i ++){
for(int j = i; j <= n; j ++){
dp[j] = (dp[j] + dp[j - i]) % mod;
}
}
printf("%lld
", dp[n] - 1);
return 0;
}