动态规划_背包问题

一.01背包问题

1.01背包问题
有 N 件物品和一个容量是 V的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

二维:f[i][j]的最大值为两种选法的最大值即f[i][j] = max(f[i – 1][j], f[i – 1][j – v[i]] + w[i])


#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1010;

int v[N], w[N];
int f[N][N];

int main()
{
    int n,m;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++)
        scanf("%d%d", &v[i], &w[i]);
        
    for (int i = 1; i <= n; i ++)
        for (int j = 0; j <= m; j ++)
        {
            f[i][j] = f[i - 1][j];
            if (j >= v[i]) f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
        }
    
    printf("%d", f[n][m]);
    
    return 0;
}

一维:f[j]状态表示:物品为n,背包容量为j下的最优解

逆序原因:在二维状态下,f[i][j]是由上一层状态i – 1来更新,如果我们在一维状态下仍用正序,f[j]由f[j – v[i]]来更新,即f[较大状态]由f[较小状态]更新,则f[j – v[i]]已经被更新为第i层的值,无法用来更新。但逆序后,j由从大到小遍历,则f[j]被f[j – v[i]]更新时,f[j – v[i]]还没有被更新过即是i – 1层的值,则正确。


#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1010;

int v[N], w[N];
int f[N];

int main()
{
    int n,m;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++)
        scanf("%d%d", &v[i], &w[i]);
    
    for (int i = 1; i <= n; i ++)    
        for (int j = m; j >= v[i]; j --)
            f[j] = max(f[j], f[j - v[i]] + w[i]);
    
    printf("%d", f[m]);
    
    return 0;
}

1024. 装箱问题

1024. 装箱问题
有一个箱子容量为 V,同时有 n 个物品,每个物品有一个体积(正整数)。
要求 n 个物品中,任取若干个装入箱内,使箱子的剩余空间为最小。
输入格式
第一行是一个整数 V,表示箱子容量。
第二行是一个整数 n,表示物品数。
接下来 n 行,每行一个正整数(不超过10000),分别表示这 n 个物品的各自体积。
输出格式
一个整数,表示箱子剩余空间。
数据范围
0<V≤20000
0<n≤30
输入样例:
24
6
8
3
12
7
9
7
输出样例:
0

//思路:将物品的体积看为价值,则最大价值就是最大的体积,答案为总体积减最大体积。

#include <bits/stdc++.h>

using namespace std;

const int N = 200010;
int f[N];

int main()
{
    int m, n, v;
    cin >> m >> n;
    for (int i = 0; i < n; i ++)
    {
        cin >> v;
        for (int j = m; j >= v; j --) f[j] = max(f[j], f[j - v] + v);
    }
    cout << m - f[m];
}

278. 数字组合

278. 数字组合
给定 N 个正整数 A1,A2,…,AN 从中选出若干个数,使它们的和为 M,求有多少种选择方案。
输入格式
第一行包含两个整数 N 和 M。
第二行包含 N 个整数,表示 A1,A2,…,AN。
输出格式
包含一个整数,表示可选方案数。

f[i][j] 的方案数为两种情况相加即 f[i][j] = f[i – 1][j] + f[i – 1][j – v],一维优化代码如下


#include <bits/stdc++.h>

using namespace std;

const int M = 10010;

int f[M];

int main()
{
    int n, m;
    cin >> n >> m;
    
    f[0] = 1;     //注:一件物品不选也是一种方案

    for (int i = 0; i < n; i ++)
    {
        int v;
        cin >> v;
        for (int j = m; j >= v; j --)
            f[j] += f[j - v];
    }
    
    cout << f[m];
    
    return 0;
}

734. 能量石

岩石怪物杜达生活在魔法森林中,他在午餐时收集了N 块能量石准备开吃。
由于他的嘴很小,所以一次只能吃一块能量石。
能量石很硬,吃完需要花不少时间。
吃完第 i 块能量石需要花费的时间为 Si 秒。
杜达靠吃能量石来获取能量。
不同的能量石包含的能量可能不同。
此外,能量石会随着时间流逝逐渐失去能量。
第 i 块能量石最初包含 Ei 单位的能量,并且每秒将失去Li 单位的能量。
当杜达开始吃一块能量石时,他就会立即获得该能量石所含的全部能量(无论实际吃完该石头需要多少时间)。
能量石中包含的能量最多降低至 0。
请问杜达通过吃能量石可以获得的最大能量是多少?

输入格式
第一行包含整数 T,表示共有 T 组测试数据。
每组数据第一行包含整数 N,表示能量石的数量。
接下来N 行,每行包含三个整数 Si,Ei,Li。
输出格式
每组数据输出一个结果,每个结果占一行。
结果表示为 Case #x: y,其中 x 是组别编号(从1 开始),y 是可以获得的最大能量值。
数据范围
1≤T≤10
1≤N≤100
1≤Si≤100
1≤Ei≤105
0≤Li≤105

输入样例:
3
4
20 10 1
5 30 5
100 30 1
5 80 60
3
10 4 1000
10 3 1000
10 8 1000
2
12 300 50
5 200 0
输出样例:
Case #1: 105
Case #2: 8
Case #3: 500

思路:贪心+dp

贪心将问题转化

发现有可能存在最优解的某些宝石的贡献为0,我们剔除了这些宝石。

假设最优解的能量石排列长度为k(1<=k<=n) 因为去掉了那些没有贡献的宝石,位置为:

a1,a2,a3…aka1,a2,a3…ak。

那么对于任意两个位置i=al,j=al+1(1<=l<k)

交换后两个宝石的贡献总和不会变得更大,即(假设之前的总时间为t ):

Ei−t∗Li+Ej−(t+Si)∗Lj>=Ej−t∗Lj+Ei−(t+Sj)∗LiEi−t∗Li+Ej−(t+Si)∗Lj>=Ej−t∗Lj+Ei−(t+Sj)∗Li

整理后:Si∗Lj<=Sj∗Li。

我们可以把跟i有关的放到一边,调整一下: Si/Li<=Sj/Lj

这样,我们只要以如上条件作为宝石间排序的条件,进行一次sort。

因为对于其他形式的放置规律,必然可以通过交换满足SiLj>SjLi的相邻的两项来得到更小值。

那么最优解的坐标(新的坐标)一定满足: ai<a2<a3…<ak


#include <bits/stdc++.h>

using namespace std;

const int N = 10010;

struct Stone
{
    int s, e, l;
    bool operator < (const Stone &W)const       //找到最优解的排序
    {
        return s * W.l < l * W.s;
    }
}stone[N];

int f[N];

int main()
{
    int t;
    cin >> t;
    for (int c = 1; c <= t; c ++)
    {
        int n, m = 0;
        cin >> n;
        for (int i = 0; i < n; i ++)
        {
            cin >> stone[i].s >> stone[i].e >> stone[i].l;
            m += stone[i].s;
        }
        
        sort(stone, stone + n);             //最优解的排序(上面已经证明)
        
        memset(f, -0x3f, sizeof f); 
        f[0] = 0;
        
        for (int i = 0; i < n; i ++)        //01背包问题
        {
            int s = stone[i].s, e = stone[i].e, l = stone[i].l;
            for (int j = m; j >= s; j --)
                f[j] = max(f[j], f[j - s] + e - (j - s) * l); //注意这里的价值是e-(j-s)*l
        }
        
        int res = 0;
        for (int i = 0; i <= m; i ++) res = max(res, f[i]);
        
        printf("Case #%d: %d\n", c, res);
    }
    
    return 0;
}

二维费用问题

有 N 件物品和一个容量是 V 的背包,背包能承受的最大重量是 M。
每件物品只能用一次。体积是 vi,重量是 mi,价值是 wi。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,总重量不超过背包可承受的最大重量,且价值总和最大。
输出最大价值。

三维:f[i][j][k]的最大值为f[i][j][k] = max(f[i – 1][j][k], f[i – 1][j – v[i]][k – m[i]] + w[i])

二维:f[j][k]表示物品为n下,体积最大为j,重量最大为k的状态下的最优解,代码如下


#include <bits/stdc++.h>

using namespace std;

const int N = 1010;

int n, V, M;
int f[N][N];

int main()
{
    cin >> n >> V >> M;
    int v, m, w;
    for (int i = 0; i < n; i ++)
    {
        cin >> v >> m >> w;
        for (int j = V; j >= v; j --)
            for (int k = M; k >= m; k --)
                f[j][k] = max(f[j][k], f[j - v][k - m] + w);
    }
    
    cout << f[V][M];
    
    return 0;
}

1022. 宠物小精灵之收服

宠物小精灵是一部讲述小智和他的搭档皮卡丘一起冒险的故事。
一天,小智和皮卡丘来到了小精灵狩猎场,里面有很多珍贵的野生宠物小精灵。
小智也想收服其中的一些小精灵。
然而,野生的小精灵并不那么容易被收服。
对于每一个野生小精灵而言,小智可能需要使用很多个精灵球才能收服它,而在收服过程中,野生小精灵也会对皮卡丘造成一定的伤害(从而减少皮卡丘的体力)。
当皮卡丘的体力小于等于0时,小智就必须结束狩猎(因为他需要给皮卡丘疗伤),而 使得皮卡丘体力小于等于0的野生小精灵也不会被小智收服。
当小智的精灵球用完时,狩猎也宣告结束。
我们假设小智遇到野生小精灵时有两个选择:收服它,或者离开它。
如果小智选择了收服,那么一定会扔出能够收服该小精灵的精灵球,而皮卡丘也一定会受到相应的伤害;如果选择离开它,那么小智不会损失精灵球,皮卡丘也不会损失体力。
小智的目标有两个:主要目标是收服尽可能多的野生小精灵;如果可以收服的小精灵数量一样,小智希望皮卡丘受到的伤害越小(剩余体力越大),因为他们还要继续冒险。
现在已知小智的精灵球数量和皮卡丘的初始体力,已知每一个小精灵需要的用于收服的精灵球数目和它在被收服过程中会对皮卡丘造成的伤害数目。
请问,小智该如何选择收服哪些小精灵以达到他的目标呢?

输入格式
输入数据的第一行包含三个整数:N,M,K,分别代表小智的精灵球数量、皮卡丘初始的体力值、野生小精灵的数量。
之后的K行,每一行代表一个野生小精灵,包括两个整数:收服该小精灵需要的精灵球的数量,以及收服过程中对皮卡丘造成的伤害。
输出格式
输出为一行,包含两个整数:C,R,分别表示最多收服C个小精灵,以及收服C个小精灵时皮卡丘的剩余体力值最多为R。

数据范围
0<N≤1000
0<M≤500
0<K≤100
输入样例1:
10 100 5
7 10
2 40
2 50
1 20
4 20
输出样例1:
3 30

#include <bits/stdc++.h>

using namespace std;

const int N = 1010, M = 510;
int f[N][M];    
int n, V1, V2;  //V1表示精灵球数量,V2表示皮卡丘体力

int main()
{
    cin >> V1 >> V2 >> n;
    int v1, v2;
    for (int i = 0; i < n; i ++)
    {
        cin >> v1 >> v2;
        for (int j = V1; j >= v1; j --)
            for (int k = V2 - 1; k >= v2; k --)     //因为皮卡丘体力至少余1,即最大为V2-1
                f[j][k] = max(f[j][k], f[j - v1][k - v2] + 1);
    }
    
    cout << f[V1][V2 - 1];                  //f[V1][V2 - 1]表示的是不超过V1,不超过V2-1下 最大值
    int k = V2 - 1;                                
    while (k > 0 && f[V1][k - 1] == f[V1][V2 - 1]) k --;    //找到为最大值前提下,最小的k
    cout << ' ' << V2 - k;
    
    return 0;
}

1020. 潜水员

1020. 潜水员
潜水员为了潜水要使用特殊的装备。
他有一个带2种气体的气缸:一个为氧气,一个为氮气。
让潜水员下潜的深度需要各种数量的氧和氮。
潜水员有一定数量的气缸。
每个气缸都有重量和气体容量。
潜水员为了完成他的工作需要特定数量的氧和氮。
他完成工作所需气缸的总重的最低限度的是多少?
例如:潜水员有5个气缸。每行三个数字为:氧,氮的(升)量和气缸的重量:

3 36 120

10 25 129

5 50 250

1 45 130

4 20 119
如果潜水员需要5升的氧和60升的氮则总重最小为249(1,2或者4,5号气缸)。
你的任务就是计算潜水员为了完成他的工作需要的气缸的重量的最低值。

f[i][j][k]中包含第i个物品时,j – v1和k – v2不用大于0,即j < v1与k < v2是合法的,因为集合代表的是体积至少为,(若体积是恰好是,那么上述值必须为正, 若体积是不超过,那么上述为非负)

由于f[0][j][k]的情况不存在,因此我们将值初始化为正无穷(我们求的是最小值),f[0][0][0] = 0

注意:遍历物品个数时,从1开始


#include <bits/stdc++.h>

using namespace std;

const int N = 30, M = 80;

int f[N][M];

int main()
{
    memset(f, 0x3f, sizeof f);
    f[0][0] = 0;
    
    int n, m, t;
    cin >> n >> m >> t;
    for (int i = 1; i <= t; i ++)
    {
        int v1, v2, w;
        cin >> v1 >> v2 >> w;
        for (int j = n; j >= 0; j --)
            for (int k = m; k >= 0; k --)
                f[j][k] = min(f[j][k], f[max(0, j - v1)][max(0, k - v2)] + w);
    }
    
    cout << f[n][m];
    
    return 0;
}

二.完全背包问题

3. 完全背包问题
有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用。
第 i 种物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i种物品的体积和价值。
输出格式
输出一个整数,表示最大价值。

注:s为每一个物品在体积最大为j下可以取到的最大值

f[i][j] = max(f[i-1][j], f[i-1][j-v]+w, f[i-1][j-2v]+2w, …, f[i-1][j-(s-1)v]+(s-1)w, f[i-1][j-sv]+sw)


#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1010;
int v[N], w[N], f[N][N], n, m;

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i ++) scanf("%d%d", &v[i], &w[i]);

    for (int i = 1; i <= n; i ++)
        for (int j = 0; j <= m; j ++)
            for (int k = 0; k * v[i] <= j; k ++)
                f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + k * w[i]);

    cout << f[n][m];

    return 0;
}

优化:减去一重循环(枚举当前物品选多少个的循环)

f[i][j] = max(f[i-1][j], f[i-1][j-v]+w, f[i-1][j-2v]+2w, …, f[i-1][j-(s-1)v]+(s-1)w, f[i-1][j-sv]+sw)

f[i][j-v] = max( f[i-1][j-v], f[i-1][j-2v]+w, …, f[i-1][j-(s-1)v]+(s-2)w, f[i-1][j-sv]+(s-1)w)

用下面蓝色部分都加上w等于上面绿色部分即f[i][j] = max(f[i – 1][j] , f[i][j – v] + w)

#include <bits/stdc++.h>

using namespace std;

const int N = 1010;
int v[N], w[N], f[N][N], n, m;

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i ++) scanf("%d%d", &v[i], &w[i]);

    for (int i = 1; i <= n; i ++)
        for (int j = 0; j <= m; j ++)
        {
            f[i][j] = f[i - 1][j];
            if (j >= v[i]) f[i][j] = max(f[i][j], f[i][j - v[i]] + w[i]);
        }

    cout << f[n][m];

    return 0;
}

优化:f[j] = max(f[j], f[j – v] + w),因为是第i层的j – v,所以不需要像01背包问题一样逆序


#include <bits/stdc++.h>

using namespace std;

const int N = 1010;

int f[N];

int main()
{
    int n, m;
    cin >> n >> m;
    for (int i = 0; i < n; i ++)
    {
        int v, w;
        cin >> v >> w;
        for (int j = v; j <= m; j ++)
            f[j] = max(f[j], f[j - v] + w);
    }
    
    cout << f[m];
    
    return 0;
}

1023. 买书

1023. 买书
小明手里有n元钱全部用来买书,书的价格为10元,20元,50元,100元。
问小明有多少种买书方案?(每种书可购买多本)
输入格式
一个整数 n,代表总共钱数。
输出格式
一个整数,代表选择方案种数。

f[i][j] = f[i – 1][j] + f[i-1][j-v] + [i-1][j-2v] + … + f[i-1][j-(s-1)v] + f[i-1][j-sv]

f[i][j – v] = f[i-1][j-v] + f[i-1][j-2v] + … + f[i-1][j-(s-1)v] + f[i-1][j-sv]

用下式替换上式绿色部分得 f[i][j] = f[i – 1][j] + f[i][j – v]


#include <bits/stdc++.h>

using namespace std;

const int N = 1010;

int f[5][N];
int v[5] = {0, 10, 20, 50, 100};

int main()
{
    int n;
    cin >> n;

    f[0][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 >= v[i]) f[i][j] += f[i][j - v[i]];
        }
    }

    cout << f[4][n];

    return 0;
}

一维优化:


#include <bits/stdc++.h>

using namespace std;

const int N = 1010;

int f[N];
int v[5] = {0, 10, 20, 50, 100};

int main()
{
    int n;
    cin >> n;
    
    f[0] = 1;
    for (int i = 1; i <= 4; i ++)
    {
        for (int j = 0; j <= n; j ++)
            if (j >= v[i]) f[j] += f[j - v[i]];
    }
    
    cout << f[n];
    
    return 0;
}

532. 货币系统

在网友的国度中共有 n 种不同面额的货币,第 i 种货币的面额为 a[i],你可以假设每一种货币都有无穷多张。
为了方便,我们把货币种数为 n、面额数组为 a[1..n] 的货币系统记作 (n,a))。 
在一个完善的货币系统中,每一个非负整数的金额 x 都应该可以被表示出,即对每一个非负整数 x,都存在 n 个非负整数 t[i] 满足 a[i]×t[i] 的和为 x。
然而,在网友的国度中,货币系统可能是不完善的,即可能存在金额 x 不能被该货币系统表示出。
例如在货币系统 n=3, a=[2,5,9] 中,金额 1,3 就无法被表示出来。 
两个货币系统 (n,a) 和(m,b) 是等价的,当且仅当对于任意非负整数 x,它要么均可以被两个货币系统表出,要么不能被其中任何一个表出。 
现在网友们打算简化一下货币系统。
他们希望找到一个货币系统(m,b),满足(m,b) 与原来的货币系统 (n,a) 等价,且 m 尽可能的小。
他们希望你来协助完成这个艰巨的任务:找到最小的 m。

输入格式
输入文件的第一行包含一个整数 T,表示数据的组数。
接下来按照如下格式分别给出 T 组数据。 
每组数据的第一行包含一个正整数 n。
接下来一行包含 n 个由空格隔开的正整数 a[i]。
输出格式
输出文件共有 T 行,对于每组数据,输出一行一个正整数,表示所有与 (n,a) 等价的货币系统 (m,b) 中,最小的 m。

数据范围
1≤n≤100
1≤a[i]≤25000
1≤T≤20
输入样例:
2
4
3 19 10 6
5
11 29 13 19 17
输出样例:
2
5

思路:即求货币系统 (n,a) 的最大无关向量组,即任意 ai 都不能被其他向量线性表出

对于一个无效的元素ai,他只会被小于他的元素的线性组合表出,满足该要求的 ai就要被筛掉

因此先进行排序,然后遍历每一个元素

当当前元素a[i]已经存在(即f[a[i]]为1)则跳过;不存在就将该元素加入无关向量组,并把他以及他和此前的硬币的线性组合都筛掉

即就是在完全背包求方案数的过程中,统计那些初始没有方案数的物品


#include <bits/stdc++.h>

using namespace std;

const int N = 110, M = 25010;

int a[N], f[M];        //由以上分析可知f数组的范围为M

int main()
{
    int t, n;
    cin >> t;
    
    while (t --)
    {
        int res = 0;
        cin >> n;
        for (int i = 0; i < n; i ++) cin >> a[i];
        sort(a, a + n);                 //排序原因见上
        
        memset(f, 0, sizeof f);         //多组数据记得初始化
        f[0] = 1;
        
        int m = a[n - 1];               //m即为数组最大值
        for (int i = 0; i < n; i ++)
        {
            if (!f[a[i]]) res ++;       //f[a[i]]==0 即a[i]不在无关向量组,应该加入
            for (int j = a[i]; j <= m; j ++)    //然后将后面的可以用无关向量组表示的都筛掉
                f[j] += f[j - a[i]];
        }
        
        cout << res << endl;
    }
    
    return 0;
}

三.多重背包问题

有 N 种物品和一个容量是 V 的背包。
第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。

即比01背包问题多一重循环来寻找每个物品选多少个为最优解

注意:多重背包问题是f[i][j] = max(f[i][j], f[i – 1][j – v * k] + w * k);(当k=0时即为不选第i个物品)

完全背包问题是f[i][j] = max(f[i – 1][j] , f[i][j – v] + w)

基础模板


#include <bits/stdc++.h>

using namespace std;

const int N = 110;

int f[N][N];

int main()
{
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; i ++)
    {
        int v, w, s;
        cin >> v >> w >> s;
        for (int j = 0; j <= m; j ++)
            for (int k = 0; k <= s && k * v <= j; k ++)
                f[i][j] = max(f[i][j], f[i - 1][j - v * k] + w * k);
    }
    
    cout << f[n][m];
    
    return 0;
}

基础模板优化

注意f[j]是由上一层i-1的值更新的,所以需和01背包一样倒序遍历体积


#include <bits/stdc++.h>

using namespace std;

const int N = 110;

int f[N];

int main()
{
    int n, m;
    cin >> n >> m;
    for (int i = 0; i < n; i ++)
    {
        int v, w, s;
        cin >> v >> w >> s;
        for (int j = m; j >= v; j --)
            for (int k = 0; k <= s && k * v <= j; k ++)
                f[j] = max(f[j], f[j - k * v] + k * w);
    }
    
    cout << f[m];
    
    return 0;
}

二进制优化

思路:1.首先将多重背包问题中每一个物品数都拆成若干个物品,那么就是01背包问题

           2.按二进制将物品数s拆分,拆分出的数只可以使用0或1数次,仅可以从0表示到s。

            拆出的数除了最后一个数,都是2的次方。

           eg: 7 = 1 + 2 + 4,(且1,2, 4 可以表示从0到7的所有数)

           3.拆分方法:用2的次方去拆分,直到无法再使用下一个2的次方,加入所剩差值


#include <bits/stdc++.h>

using namespace std;

const int N = 11010, M = 2010;  // 注N需要开大些

int v[N], w[N];
int f[M];

int main()
{
    int n, m;
    cin >> n >> m;
    
    int cnt = 0;
    for (int i = 0; i < n; i ++)
    {
        int a, b, s;
        cin >> a >> b >> s;
        for (int k = 1; k <= s; k *= 2)
        {
            s -= k;
            cnt ++;
            v[cnt] = k * a;
            w[cnt] = k * b;
        }
        if (s > 0)
        {
            cnt ++;
            v[cnt] = s * a;
            w[cnt] = s * b;
        }
    }
    
    for (int i = 1; i <= cnt; i ++)
        for (int j = m; j >= v[i]; j --)
            f[j] = max(f[j], f[j - v[i]] + w[i]);
            
    cout << f[m] ;
    
    return 0;
}

单调队列优化

四.分组背包问题

注意:分组背包问题是同一组内的物品最多只能选一个

有 N 组物品和一个容量是 V 的背包。
每组物品有若干个, 同一组内的物品最多只能选一个。
每件物品的体积是 vij,价值是 wij,其中 i 是组号,j 是组内编号。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。
输出最大价值。

#include <bits/stdc++.h>

using namespace std;

const int N = 110;

int n, m;
int s[N];
int v[N][N], w[N][N], f[N][N];

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i ++)
    {
        cin >> s[i];
        for (int j = 1; j <= s[i]; j ++)
            cin >> v[i][j] >> w[i][j];
    }
    
    for (int i = 1; i <= n; i ++)
        for (int j = 0; j <= m; j ++)
        {
            f[i][j] = f[i - 1][j];    //不选这组物品
            for (int k = 1; k <= s[i]; k ++)    //遍历选这组哪一个
                if (j >= v[i][k]) f[i][j] = max(f[i][j], f[i - 1][j - v[i][k]] + w[i][k]);
        }
    
    cout << f[n][m];
    
    return 0;
}

根据上面f[i][j]由f[i – 1][j]更新,所以可以倒序遍历体积优化掉一维


#include <bits/stdc++.h>

using namespace std;

const int N = 110;

int s[N], f[N];
int v[N][N], w[N][N];

int main()
{
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; i ++)
    {
        cin >> s[i];
        for (int j = 0; j < s[i]; j ++)
            cin >> v[i][j] >> w[i][j];
    }
    
    for (int i = 1; i <= n; i ++)
        for (int j = m; j >= 0; j --)
            for (int k = 0; k < s[i]; k ++)
                if (j >= v[i][k]) f[j] = max(f[j], f[j - v[i][k]] + w[i][k]);   
                
    cout << f[m];
    
    return 0;
}

1013. 机器分配

总公司拥有M 台 相同 的高效设备,准备分给下属的N 个分公司。
各分公司若获得这些设备,可以为国家提供一定的盈利。盈利与分配的设备数量有关。
问:如何分配这M台设备才能使国家得到的盈利最大?
求出最大盈利值。
分配原则:每个公司有权获得任意数目的设备,但总台数不超过设备数 M。

输入格式
第一行有两个数,第一个数是分公司数 N,第二个数是设备台数 M;
接下来是一个 N×M 的矩阵,矩阵中的第 i 行第 j 列的整数表示第 i 个公司分配 j 台机器时的盈利。
输出格式
第一行输出最大盈利值;
接下 N 行,每行有 2 个数,即分公司编号和该分公司获得设备台数。
答案不唯一,输出任意合法方案即可。

数据范围
1≤N≤10
1≤M≤15
输入样例:
3 3
30 40 50
20 30 50
20 25 30
输出样例:
70
1 1
2 1
3 1

思路:

每家公司 都看作一个 物品组,因为 每家公司 最终能够被分配的 机器数量 是固定且唯一的

(即满足每一物品组只选一个物品)

因此对于分给第 i 个公司的选择不同机器数量可以分别看作是一个物品组内选出的物品,就是一道分组背包问题

第二问求合法方案 则倒序遍历,用一个数组记下每一个公司选择的机器数量


#include <bits/stdc++.h>

using namespace std;

const int N = 20;

int way[N];
int w[N][N];
int f[N][N];

int main()
{
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; i ++)        
        for (int j = 1; j <= m; j ++)   
            cin >> w[i][j];
            
    for (int i = 1; i <= n; i ++)            //循环物品组
        for (int j = 1; j <= m; j ++)        //循环体积
        {
            f[i][j] = f[i - 1][j];
            for (int k = 1; k <= j; k ++)    //循环决策
                f[i][j] = max(f[i][j], f[i - 1][j - k] + w[i][k]);
        }
        
    cout << f[n][m] << endl;
    
    int j = m;
    for (int i = n; i; i --)
        for (int k = 0; k <= j; k ++)
        {
            if (f[i][j] == f[i - 1][j - k] + w[i][k])
            {
                way[i] = k;
                j -= k;
                break;
            }
        }
    
    for (int i = 1; i <= n; i ++) cout << i << ' ' << way[i] << endl;
    
    return 0;
}

10. 有依赖的背包问题

10. 有依赖的背包问题
有 N 个物品和一个容量是 V 的背包。
物品之间具有依赖关系,且依赖关系组成一棵树的形状。如果选择一个物品,则必须选择它的父节点。
如下图所示:
如果选择物品5,则必须选择物品1和2。这是因为2是5的父节点,1是2的父节点。
每件物品的编号是 i,体积是 vi,价值是 wi,依赖的父节点编号是 pi。物品的下标范围是 1…N。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。
输出最大价值。

将该问题转化为分组背包问题,用dfs遍历每一个节点,该节点下的每一个儿子节点为一组,在组内按照体积划分,求出最大值,注意最后加上根节点的值,因为只有选择了根节点,才可以选择子节点。


#include <bits/stdc++.h>

using namespace std;

const int N = 110;

int n, m;
int v[N], w[N];
int e[N], ne[N], h[N], idx;
int f[N][N];

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}

void dfs(int u)
{
    for (int i = h[u]; ~i; i = ne[i])         //循环物品组
    {
        int son = e[i];
        dfs(e[i]);
        
        //分组背包
        for (int j = m - v[u]; j >= 0; j --)  //循环体积
            for (int k = 0; k <= j; k ++)     //循环决策
                f[u][j] = max(f[u][j], f[u][j - k] + f[son][k]);
    }
    //将物品u加入
    for (int j = m; j >= v[u]; j --) f[u][j] = f[u][j - v[u]] + w[u];
    for (int j = 0; j < v[u]; j ++) f[u][j] = 0;    //清空没选上u的所有状态
}

int main()
{
    int root;
    memset (h, -1, sizeof h);
    
    cin >> n >> m;
    for (int i = 1; i <= n; i ++)
    {
        int p;
        cin >> v[i] >> w[i] >> p;
        if (p == -1) root = i;
        else add(p, i);
    }
    
    dfs(root);
    
    cout << f[root][m];
    
    return 0;
}

487. 金明的预算方案

金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间金明自己专用的很宽敞的房间。
更让他高兴的是,妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过N元钱就行”。
今天一早,金明就开始做预算了,他把想买的物品分为两类:主件与附件,附件是从属于某个主件的,下表就是一些主件与附件的例子:
如果要买归类为附件的物品,必须先买该附件所属的主件。
每个主件可以有0个、1个或2个附件。
附件不再有从属于自己的附件。
金明想买的东西很多,肯定会超过妈妈限定的N元。
于是,他把每件物品规定了一个重要度,分为5等:用整数1~5表示,第5等最重要。
他还从因特网上查到了每件物品的价格(都是10元的整数倍)。
他希望在不超过N元(可以等于N元)的前提下,使每件物品的价格与重要度的乘积的总和最大。
设第j件物品的价格为v[j],重要度为w[j],共选中了k件物品,编号依次为j1,j2,…,jk,则所求的总和为:
v[j1]∗w[j1]+v[j2]∗w[j2]+…+v[jk]∗w[jk](其中*为乘号)
请你帮助金明设计一个满足要求的购物单。

输入格式
输入文件的第1行,为两个正整数,用一个空格隔开:N m,其中N表示总钱数,m为希望购买物品的个数。
从第2行到第m+1行,第j行给出了编号为j-1的物品的基本数据,每行有3个非负整数v p q,其中v表示该物品的价格,p表示该物品的重要度(1~5),q表示该物品是主件还是附件。
如果q=0,表示该物品为主件,如果q>0,表示该物品为附件,q是所属主件的编号。
输出格式
输出文件只有一个正整数,为不超过总钱数的物品的价格与重要度乘积的总和的最大值(<200000)。
数据范围
N<32000,m<60,v<10000
输入样例:
1000 5
800 2 0
400 5 1
300 5 1
400 3 0
500 2 0
输出样例:
2200

思路:

可以将每个主件及其附件看作一个物品组,记主件为 p,两个附件为 a,b, 则最多一共有4种组合:

p

p, a

p, b

p, a, b

这四种组合是互斥的,最多只能从中选一种,因此可以将每种组合看作一个物品,那么问题就变成了分组背包问题。

在枚举四种组合时可以使用二进制的思想,可以简化代码。


#include <bits/stdc++.h>

using namespace std;

typedef pair<int, int> PII;

const int N = 70, M = 32010;

int f[M];
PII master[N];
vector<PII> slaver[N];

int main()
{
    int n, m;
    cin >> m >> n;
    for (int i = 1; i <= n; i ++)
    {
        int v, p, q;
        cin >> v >> p >> q;
        if (!q) master[i] = {v, v * p};
        else slaver[q].push_back({v, v * p});
    }
    
    for (int i = 1; i <= n; i ++)               //遍历物品
        for (int j = m; j >= 0; j --)           //遍历体积
        {
            for (int k = 0; k < 1 << slaver[i].size(); k ++)    //遍历决策
            {
                int v = master[i].first, w = master[i].second;
                for (int t = 0; t < slaver[i].size(); t ++)     
                {
                    if (k >> t & 1)             //当该位置为1则表示选择该物品
                    {
                        v += slaver[i][t].first;
                        w += slaver[i][t].second;
                    }
                }
                if (j >= v) f[j] = max(f[j], f[j - v] + w);     //更新f[j]
            }
        }
    
    cout << f[m];
    
    return 0;
}

五.混合背包问题


#include <bits/stdc++.h>

using namespace std;

const int N = 20010;

int f[N];

int main()
{
    int n, m;
    cin >> n >> m;
    for (int i = 0; i < n; i ++)
    {
        int v, w, s;
        cin >> v >> w >> s;
        
        if (s == 0)    //完全背包
        {
            for (int j = v; j <= m; j ++)
                f[j] = max(f[j], f[j - v] + w);
        }
        else
        {        //多重背包用二进制优化,转为01背包
            if (s == -1) s = 1;  
            for (int k = 1; k <= s; k *= 2)
            {
                for (int j = m; j >= v * k; j --)
                    f[j] = max(f[j], f[j - v * k] + w * k);
                s -= k;
            }
            if (s)
            {
                for (int j = m; j >= v * s; j --)
                    f[j] = max(f[j], f[j - v * s] + w * s);
            }
        }
    }
    
    cout << f[m];
    
    return 0;
}

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
青葱年少的头像青葱年少普通用户
上一篇 2023年12月21日
下一篇 2023年12月21日

相关推荐