针对于取数字型的01背包与完全背包的一点想法


首先,我们可以先清楚
这类题目给出的是一个数能否有若干个数构成
存在可以重复取,或者不可以重复取两种情况
类似如下这样的题

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\n", 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\n", dp[n] - 1);
    return 0;
}

posted @ 2019-05-30 00:35  moxin0509  阅读(215)  评论(0编辑  收藏  举报