背包问题

01背包

例题糖果

这是一道有限制选择问题,可以类比01背包的思路来考虑这道题

WechatIMG828.png

#include <cstring>
#include <iostream>

using namespace std;
const int N = 110;
//用滚动数组进行空间的优化
int f[2][N], w[N], n, k;

int main()
{
    cin >> n >> k;
    for(int i = 1; i <= n; ++ i)    cin >> w[i];
    //将其他状态均设置为不合法状态,初始状态只有f[0][0] = 0
    memset(f, -0x3f, sizeof f);
    //错误的初始化方法,当一个都不选的话,mod k的余数只能是0
    // for(int i = 0; i < k; ++ i) f[0][i] = 0;
    f[0][0] = 0;
    for(int i = 1; i <=n ; ++ i)
        for(int j = 0; j < k; ++ j)
            f[i & 1][j] = max(f[(i - 1) & 1][j], f[(i - 1) & 1][(j  + k - w[i] % k) % k] + w[i]); 
    
    cout << f[n & 1][0] << endl;
    return 0;
}

完全背包

例题包子凑数

裴蜀定理

对于任意整数a,b,存在一对整数x,y,满足ax+by=gcd(a, b),推广至n个数也成立

当这n个数的最大公约数(记为d)不为一时,我们能凑出来的数,只能是这n个数的最大公约数的倍数,所以所有因数不包含d的数一定不能被这n个数凑出来,并且这个数的倍数(不包含d因子)的数也都不能被凑出来,所以当最大公约数不是1时答案一定是INF

当最大公约数是1时,我们能凑出的数一定是1的倍数,我们知道1是任何数的公因子,所以此时,只有有限个数不能被凑出来。

那么最大的不能凑出来的数是多少?我们知道对于两个数而言gcd(a, b) = 1;a,b不能凑出来的最大的数是(a - 1)*(b - 1) - 1,而数越多,我们能凑出来的数就越多,这个上界也将越小,所以我们寻找100以内两个最大的互质的数99, 98,所以我们将上界定位1e4即可(当然更大也行,不过要考虑一下时间复杂度,不能超时了),分析自此我们这道题的难点已经解决了,剩下的就是跑一遍完全背包即可。

完全背包闫氏dp分析法

WechatIMG840.png

关于完全背包的优化,可以用滚动数组,也可以直接优化至一维,我们用到的状态有f[i - 1][j]和f[i][j - wi],因为我们在更新时需要用到体积更大的状态是未更新的,应该正序循环体积

当我们枚举只j时,[0,j]存储的是第i层的状态,也就是我们已经更新过的状态,[j, M]存储的是第i - 1层的状态。

二维未优化

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>

using namespace std;

const int N = 110, M = 1e5 + 10;
//f[i][j]表示考虑前i个物品,总体积恰好为j的所有方案
int n, w[N], f[N][M];

int gcd(int a, int b)
{
    return b == 0 ? a : gcd(b, a % b);
}
int main()
{
    cin >> n;
    int d = 0;
    for(int i = 1; i<= n; ++ i)
    {
        scanf("%d", &w[i]);
        d = gcd(w[i], d);
    }
    
    if(d != 1) puts("INF");
    else
    {
        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 >= w[i])   f[i][j] |= f[i][j - w[i]];
            }
        
        int res = 0;
        for(int i = 0; i <= M; ++ i)
            if(!f[n][i])    res ++;
        cout << res << endl;
    }
    return 0;
}

滚动数组优化

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>

using namespace std;

const int N = 110, M = 1e5 + 10;
//f[i][j]表示考虑前i个物品,总体积恰好为j的所有方案
int n, w[N], f[2][M];

int gcd(int a, int b)
{
    return b == 0 ? a : gcd(b, a % b);
}
int main()
{
    cin >> n;
    int d = 0;
    for(int i = 1; i<= n; ++ i)
    {
        scanf("%d", &w[i]);
        d = gcd(w[i], d);
    }
    
    if(d != 1) puts("INF");
    else
    {
        f[0 & 1][0] = 1;
        for(int i = 1; i <= n; ++ i)
            for(int j = 0; j <= M; ++ j)
            {
                f[i & 1][j] |= f[(i - 1) & 1][j];
                if(j >= w[i])
                    f[i & 1][j] |= f[i & 1][j - w[i]];
            }
        
        int res = 0;
        for(int i = 0; i <= M; ++ i)
            if(!f[n & 1][i])    res ++;
        cout << res << endl;
    }
    return 0;
}


一维

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>

using namespace std;

const int N = 110, M = 1e5 + 10;
//f[i][j]表示考虑前i个物品,总体积恰好为j的所有方案
int n, w[N], f[M];

int gcd(int a, int b)
{
    return b == 0 ? a : gcd(b, a % b);
}
int main()
{
    cin >> n;
    int d = 0;
    for(int i = 1; i<= n; ++ i)
    {
        scanf("%d", &w[i]);
        d = gcd(w[i], d);
    }
    
    if(d != 1) puts("INF");
    else
    {
        f[0] = 1;
        for(int i = 1; i <= n; ++ i)
            for(int j = w[i]; j <= M; ++ j)
            {
                f[j] |= f[j - w[i]];
            }
        
        int res = 0;
        for(int i = 0; i <= M; ++ i)
            if(!f[i])    res ++;
        cout << res << endl;
    }
    return 0;
}


posted @ 2023-04-04 16:36  cxy8  阅读(36)  评论(0编辑  收藏  举报