算法提高分类学习刷题——1.动态规划——1.3背包模型

在这里插入图片描述

(1)01背包

模型

题目介绍

在这里插入图片描述

有 N件物品和一个容量为 V 的背包,每件物品有各自的价值且只能被选择一次,要求在有限的背包容量下,装入的物品总价值最大。

「0-1 背包」是较为简单的动态规划问题,也是其余背包问题的基础。

动态规划是不断决策求最优解的过程,「0-1 背包」即是不断对第 i个物品的做出决策,「0-1」正好代表不选与选两种决定。

二维

(1)状态f[i][j]定义:前i个物品,背包容量j下的最优解(最大价值):

当前的状态依赖于之前的状态,可以理解为从初始状态f[0][0] = 0开始决策,有 N件物品,则需要N次决 策,每一次对第 i 件物品的决策,状态[i][j]不断由之前的状态更新而来。
(2)当前背包容量不够(j < v[i]),没得选,因此前i 个物品最优解即为前 i−1个物品最优解:
对应代码:f[i][j] = f[i - 1][j]
(3)当前背包容量够,可以选,因此需要决策选与不选第 i个物品:
选:f[i][j] = f[i - 1][j - v[i]] + w[i]
不选:f[i][j] = f[i - 1][j]
我们的决策是如何取到最大价值,因此以上两种情况取max()

代码

for(int i = 1; i <= n; i++) 
        for(int j = 1; j <= m; j++)
        {
 //  当前背包容量装不进第i个物品,则价值等于前i-1个物品
            if(j < v[i]) 
                f[i][j] = f[i - 1][j];
            // 能装,需进行决策是否选择第i个物品
            else    
                f[i][j] = max(f[i - 1][j], f[i - 1][j - v[i]] + w[i]);
        }           

一维

(1)状态f[j]定义:N 件物品,背包容量j下的最优解。

(2)注意枚举背包容量j必须从m开始。

(3)为什么一维情况下枚举背包容量需要逆序?在二维情况下,状态f[i][j]是由上一轮i - 1的状态得来的,f[i][j]与f[i - 1][j]是独立的。而优化到一维后,如果我们还是正序,则有f[较小体积]更新到f[较大体积],则有可能本应该用第i-1轮的状态却用的是第i轮的状态。(正序的话每一轮都会从体积较小的开始更新,那么f[j-v[i]]用到的就是更新过的,而不是上一轮的)

代码

//实际上,只有当枚举的背包容量 >= v[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]);
} 

例题

1.01背包问题

题目描述

有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。

第 i 件物品的体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。

接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。

输出格式
输出一个整数,表示最大价值。

数据范围
0<N,V≤1000
0<vi,wi≤1000
输入样例

4 5
1 2
2 4
3 4
4 5

输出样例:

8

解题思路

本题就是01背包的模板题,直接用模板就行

代码实现
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1010;
int v[N],w[N],f[N];
int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>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]);
        }
    }
    cout<<f[m]<<endl;
    return 0;
}

2.采药

题目描述

辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。

为此,他想拜附近最有威望的医师为师。

医师为了判断他的资质,给他出了一个难题。

医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”

如果你是辰辰,你能完成这个任务吗?

输入格式
输入文件的第一行有两个整数 T 和 M,用一个空格隔开,T 代表总共能够用来采药的时间,M 代表山洞里的草药的数目。

接下来的 M 行每行包括两个在 1 到 100 之间(包括 1 和 100)的整数,分别表示采摘某株草药的时间和这株草药的价值。

输出格式
输出文件包括一行,这一行只包含一个整数,表示在规定的时间内,可以采到的草药的最大总价值。

数据范围
1≤T≤1000,
1≤M≤100
输入样例:

70 3
71 100
69 1
1 2

输出样例:

3

解题思路

这道题是一个裸背包问题,将这道题的时间看做背包体积就可以了

代码实现
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=1010;
int t[N],w[N],f[N];
int main()
{
    int n,m;
    cin>>m>>n;
    for(int i=1;i<=n;i++) cin>>t[i]>>w[i];
    for(int i=1;i<=n;i++)
        for(int j=m;j>=t[i];j--)
            f[j]=max(f[j],f[j-t[i]]+w[i]);
    cout<<f[m]<<endl;
    return 0;
}

3.装箱问题

题目描述

有一个箱子容量为 V,同时有 n 个物品,每个物品有一个体积(正整数)。

要求 n 个物品中,任取若干个装入箱内,使箱子的剩余空间为最小。

输入格式
第一行是一个整数 V,表示箱子容量。

第二行是一个整数 n,表示物品数。

接下来 n 行,每行一个正整数(不超过10000),分别表示这 n 个物品的各自体积。

输出格式
一个整数,表示箱子剩余空间。

数据范围
0<V≤20000,
0<n≤30
输入样例:

24
6
8
3
12
7
9
7

输出样例:

0

解题思路

这道题,将物品的体积也看做价值,因为要求的箱子剩余体积最小,其实就是装的物品的体积最大,将体积看做价值,就变成了简单的01背包问题

代码实现
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=20010;
int f[N];
int main()
{
    int n,m;
    cin>>m>>n;
    for(int i=1;i<=n;i++)
    {
        int v;
        cin>>v;
        for(int j=m;j>=v;j--)
        {
            f[j]=max(f[j],f[j-v]+v);
        }
    }
    cout<<m-f[m]<<endl;//因为要求箱子最小剩余体积,所以用总体积减去最大体积就是剩余最小体积
    return 0;
}

4.二维费用的背包问题

题目描述

有 N 件物品和一个容量是 V 的背包,背包能承受的最大重量是 M。

每件物品只能用一次。体积是 vi,重量是 mi,价值是 wi。

求解将哪些物品装入背包,可使物品总体积不超过背包容量,总重量不超过背包可承受的最大重量,且价值总和最大。
输出最大价值。

输入格式
第一行三个整数,N,V,M,用空格隔开,分别表示物品件数、背包容积和背包可承受的最大重量。

接下来有 N 行,每行三个整数 vi,mi,wi,用空格隔开,分别表示第 i 件物品的体积、重量和价值。

输出格式
输出一个整数,表示最大价值。

数据范围
0<N≤1000
0<V,M≤100
0<vi,mi≤100
0<wi≤1000
输入样例

4 5 6
1 2 3
2 4 4
3 4 5
4 5 6

输出样例:

8

解题思路

在这里插入图片描述

代码实现
#include<iostream>
#include<algorithm>
using namespace std;
const int N=110;
int f[N][N];
int n,v,m;
int main()
{
    cin>>n>>v>>m;
    for(int i=1;i<=n;i++)
    {
        int vi,mi,wi;
        cin>>vi>>mi>>wi;
        for(int j=v;j>=vi;j--)
        {
            for(int k=m;k>=mi;k--)
            {
                f[j][k]=max(f[j][k],f[j-vi][k-mi]+wi);
            }
        }
    }
    cout<<f[v][m]<<endl;
    return 0;
}

5.宠物小精灵之收服

题目描述

宠物小精灵是一部讲述小智和他的搭档皮卡丘一起冒险的故事。

一天,小智和皮卡丘来到了小精灵狩猎场,里面有很多珍贵的野生宠物小精灵。

小智也想收服其中的一些小精灵。

然而,野生的小精灵并不那么容易被收服。

对于每一个野生小精灵而言,小智可能需要使用很多个精灵球才能收服它,而在收服过程中,野生小精灵也会对皮卡丘造成一定的伤害(从而减少皮卡丘的体力)。

当皮卡丘的体力小于等于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

输入样例2:

10 100 5
8 110
12 10
20 10
5 200
1 110

输出样例2:

0 100

解题思路

体积——1精灵球数量,2皮卡丘体力值 价值——小精灵的数量
在这里插入图片描述

代码实现
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=1010,M=520;
int f[N][N],v1,v2;
int main()
{
    int n;
    cin>>v1>>v2>>n;
    for(int i=0;i<n;i++)
    {
        int v3,v4;
        cin>>v3>>v4;
        for(int j=v1;j>=v3;j--)
        {
            for(int k=v2-1;k>=v4;k--)//因为皮卡丘的体力值大于0,所以皮卡丘的伤害小于v2
                f[j][k]=max(f[j][k],f[j-v3][k-v4]+1);
        }
    }
    cout<<f[v1][v2-1]<<" ";
    int k=v2-1;
    while(k>0&&f[v1][k-1]==f[v1][v2-1]) k--;
    cout<<v2-k<<endl;//k是最小伤害,所以v2-k就是最大剩余体力值
}

6.潜水员

题目描述

潜水员为了潜水要使用特殊的装备。

他有一个带2种气体的气缸:一个为氧气,一个为氮气。

让潜水员下潜的深度需要各种数量的氧和氮。

潜水员有一定数量的气缸。

每个气缸都有重量和气体容量。

潜水员为了完成他的工作需要特定数量的氧和氮。

他完成工作所需气缸的总重的最低限度的是多少?

例如:潜水员有5个气缸。每行三个数字为:氧,氮的(升)量和气缸的重量:

3 36 120

10 25 129

5 50 250

1 45 130

4 20 119
如果潜水员需要5升的氧和60升的氮则总重最小为249(1,2或者4,5号气缸)。

你的任务就是计算潜水员为了完成他的工作需要的气缸的重量的最低值。

输入格式
第一行有2个整数 m,n。它们表示氧,氮各自需要的量。

第二行为整数 k 表示气缸的个数。

此后的 k 行,每行包括ai,bi,ci,3个整数。这些各自是:第 i 个气缸里的氧和氮的容量及气缸重量。

输出格式
仅一行包含一个整数,为潜水员完成工作所需的气缸的重量总和的最低值。

数据范围
1≤m≤21,
1≤n≤79,
1≤k≤1000,
1≤ai≤21,
1≤bi≤79,
1≤ci≤800
输入样例:

5 60
5
3 36 120
10 25 129
5 50 250
1 45 130
4 20 119

输出样例:

249

解题思路

集合:从前i件物品选,氧的体积至少为j,氮的体积至少为k的方案数
属性Min
状态计算
1.选择第i件物品f[i-1][j-v1][k-v2]
2.不选择第i件物品 f[i-1][j][k]
在这里插入图片描述

根据f集合的意义可以对数组初始化(初始化的原因是只有f(0,0,0)这个状态是合法的),f(0,j,k)从前0件物品选,氧不超过j,氮不超过k的选法的最小值,肯定是0,因为没有选择物品,重量是0f(0,j,k)从前0件物品选,氧是j,氮是k的重量的最小值,一定不存在,因为不可能氧和氮有一定体积了,没有重量,设置为正无穷表示无解。
在这里插入图片描述

一般的二维费用问题,模板是

for(int j=V1,j>=a;j--)
	for(int k=V2;k>=b;k--)
		f[j][k]=max(f[j][k],f[j-a][k-b]+c);

但这道题是

for(int j=V1;j>=0;j--)
	for(int k=V2;k>=0;k--)
		f[j][k]=max(f[j][k],f[j-a][k-b]+c);

为什么这道题可以取负值,根据f分集合意义可以知道,取负数是合法的,因为当需要氧气或者氮气的体积是负的说明已经够了不需要了,那么肯定是可以取的,而且去负数时的重量可以看做是0,也就是不需要。

代码实现
#include<iostream>
#include<cstring>
using namespace std;
const int N=25,M=85;
int f[N][M];
int main()
{
    int V1,V2,n;
    cin>>V1>>V2>>n;
    memset(f,0x3f,sizeof f);
    f[0][0]=0;
    for(int i=1;i<=n;i++)
    {
        int a,b,c;
        cin>>a>>b>>c;
        for(int j=V1;j>=0;j--)
        {
            for(int k=V2;k>=0;k--)
            {
                f[j][k]=min(f[j][k],f[max(0,j-a)][max(0,k-b)]+c);
            }
        }
    }
    cout<<f[V1][V2]<<endl;
    return 0;
}

7.数学组合(01背包求方案数)

题目描述

给定 N 个正整数 A1,A2,…,AN,从中选出若干个数,使它们的和为 M,求有多少种选择方案。

输入格式
第一行包含两个整数 N 和 M。

第二行包含 N 个整数,表示 A1,A2,…,AN。

输出格式
包含一个整数,表示可选方案数。

数据范围
1≤N≤100,
1≤M≤10000,
1≤Ai≤1000,
答案保证在 int 范围内。

输入样例:

4 4
1 1 2 2

输出样例:

3

解题思路

考虑初始化,f[0][0]=1,因为总和是0,也就是一个都不选,方案数是0
在这里插入图片描述

代码实现
#include<iostream>
#include<cstring>
using namespace std;
const int N=10010;
int f[N];
int main()
{
    int n,m;
    cin>>n>>m;
    f[0]=1;
    for(int i=1;i<=n;i++)
    {
        int a;
        cin>>a;
        for(int j=m;j>=a;j--)
        {
            f[j]=f[j]+f[j-a];
        }
    }
    cout<<f[m]<<endl;
    return 0;
}

8.背包问题求具体方案

题目描述

有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。

第 i 件物品的体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。

输出 字典序最小的方案。这里的字典序是指:所选物品的编号所构成的序列。物品的编号范围是 1…N。

输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。

接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。

输出格式
输出一行,包含若干个用空格隔开的整数,表示最优解中所选物品的编号序列,且该编号序列的字典序最小。

物品编号范围是 1…N。

数据范围
0<N,V≤1000
0<vi,wi≤1000
输入样例

4 5
1 2
2 4
3 4
4 6

输出样例:

1 4

解题思路

先求解最大价值,然后由得到的最大价值来判断每件物品选了还是没选,当f[n,m]=f[n-1,m],说明第i件物品没选,当f[n,m]=f[n-1,m-vi]+w[i],说明第i件物品选了,当f[n-1,m]=f[n-1,m-vi]+w[i],说明选与不选是一样的,为了按照字典序得到最大价值,从前到后枚举时,我们就选。但是判断每件物品选与不选是从后往前枚举的,所以为了统一,在求解01背包的时候从后往前推,就是循环的时候从后往前,这样就能从第一个物品推到第n个物品
在这里插入图片描述在这里插入图片描述

代码实现
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=1010;
int w[N],v[N],f[N][N];
int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
    for(int i=n;i>=1;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]);
        }
    }
    int j=m;
    for(int i=1;i<=n;i++)
    {
        if(j>=v[i]&&f[i][j]==f[i+1][j-v[i]]+w[i])
        {
            cout<<i<<" ";
            j-=v[i];
        }
    }
    return 0;
}

9.背包问题求方案数

题目描述
解题思路
代码实现

10.开心的金明

题目描述

金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间他自己专用的很宽敞的房间。

更让他高兴的是,妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过 N 元钱就行”。

今天一早金明就开始做预算,但是他想买的东西太多了,肯定会超过妈妈限定的 N 元。

于是,他把每件物品规定了一个重要度,分为 5 等:用整数 1∼5 表示,第 5 等最重要。

他还从因特网上查到了每件物品的价格(都是整数元)。

他希望在不超过 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 的物品的基本数据,每行有 2 个非负整数 v 和 p。(其中 v 表示该物品的价格,p 表示该物品的重要度)

输出格式
输出文件只有一个正整数,为不超过总钱数的物品的价格与重要度乘积的总和的最大值(数据保证结果不超过 108)。

数据范围
1≤N<30000,
1≤m<25,
0≤v≤10000,
1≤p≤5
输入样例:

1000 5
800 2
400 5
300 5
400 3
200 2

输出样例:

3900

解题思路

这道题就是一道简单的01背包应用题,将总金额看做体积,将每件物品的金额乘重要度看做价值,直接用01背包模板做即可

代码实现
#include<iostream>
#include<algorithm>
using namespace std;
const int N=30010;
int f[N];
int n,m;
int main()
{
    cin>>m>>n;
    for(int i=1;i<=n;i++)
    {
        int v,p;
        cin>>v>>p;
        for(int j=m;j>=v;j--)
        {
            f[j]=max(f[j],f[j-v]+v*p);
        }
    }
    cout<<f[m]<<endl;
    return 0;
}

11.能量石

题目描述
解题思路
代码实现

(2)完全背包

模型

题目介绍

在这里插入图片描述

有 N件物品和一个容量为 V 的背包,每件物品有各自的价值且只能被选择一>次,要求在有限的背包容量下,装入的物品总价值最大。

「0-1 背包」是较为简单的动态规划问题,也是其余背包问题的基础。

动态规划是不断决策求最优解的过程,「0-1 背包」即是不断对第 i个物品的做出决策,「0-1」正好代表不选与选两种决定。

二维

(1)状态f[i][j]定义:前 i个物品,背包容量 j下的最优解(最大价值):

当前的状态依赖于之前的状态,可以理解为从初始状态f[0][0] = 0开始决策,>有 N件物品,则需要 N次决 策,每一次对第i 件物品的决策,状态f[i][j]不断由之前的状态更新而来。
(2)当前背包容量不够(j < v[i]),没得选,因此前i个物品最优解即为前 i−1个物品最优解:
对应代码:f[i][j] = f[i - 1][j]
(3)当前背包容量够,可以选,因此需要决策选与不选第 i个物品:
选:f[i][j] = f[i - 1][j - v[i]] + w[i]
不选:f[i][j] = f[i - 1][j]
我们的决策是如何取到最大价值,因此以上两种情况取 max()

代码

for(int i = 1; i <= n; i++) 
        for(int j = 1; j <= m; j++)
        {
 //  当前背包容量装不进第i个物品,则价值等于前i-1个物品
            if(j < v[i]) 
                f[i][j] = f[i - 1][j];
            // 能装,需进行决策是否选择第i个物品
            else    
                f[i][j] = max(f[i - 1][j], f[i - 1][j - v[i]] + w[i]);
        }           

一维

(1)状态f[j]定义:N 件物品,背包容量j下的最优解。

(2)注意枚举背包容量j必须从m开始。

(3)为什么一维情况下枚举背包容量需要逆序?在二维情况下,状态f[i][j]是由上一轮i - 1的状态得来的,f[i][j]f[i - 1][j]是独立的。而优化到一维后,如果我们还是正序,则有f[较小体积]更新到f[较大体积],则有可能本应该用第i-1轮的状态却用的是第i轮的状态。(正序的话每一轮都会从体积较小的开始更新,那么f[j-v[i]]用到的就是更新过的,而不是上一轮的)

代码

//实际上,只有当枚举的背包容量 >= v[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]);
} 

例题

1.完全背包问题

题目描述

有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用。

第 i 种物品的体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。

接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 种物品的体积和价值。

输出格式
输出一个整数,表示最大价值。

数据范围
0<N,V≤1000
0<vi,wi≤1000
输入样例

4 5
1 2
2 4
3 4
4 5

输出样例:

10

解题思路

模板题,直接用完全背包模板即可,一维二维都行

代码实现
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=1010;
int f[N],w[N],v[N];
int main()
{
    int n,V;
    cin>>n>>V;
    for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
    for(int i=1;i<=n;i++)
    {
        for(int j=v[i];j<=V;j++)
        {
            f[j]=max(f[j],f[j-v[i]]+w[i]);
        }
    }
    cout<<f[V]<<endl;
    return 0;
}

2.买书(完全背包求方案数)

题目描述
解题思路

为什么f[i,j]=f[i-1,j]+f[i,j-v]
因为:f[i,j]=f[i-1,j]+f[i-1,j-v*1]+f[i-1,j-v*2]+...+f[i-1,j-v*s]
f[i,j-v]=f[i-1,j-v*1]+f[i-1,j-v*2]+...+f[i-1,j-v*s]
初始化f[0][0]=1
这道题就是从四本书里选,总金额不超过`m``的方案数
在这里插入图片描述

代码实现

二维;

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=1010;
int f[5][N];
int v[5]={0,10,20,50,100};
int main()
{
    int m;
    cin>>m;
    f[0][0]=1;
    for(int i=1;i<=4;i++)
    {
        for(int j=0;j<=m;j++)
        {
            f[i][j]=f[i-1][j];
            if(j>=v[i]) f[i][j]+=f[i][j-v[i]];
        }
    }
    cout<<f[4][m]<<endl;
    return 0;
}

一维

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=1010;
int f[N];
int v[5]={0,10,20,50,100};
int main()
{
    int m;
    cin>>m;
    f[0]=1;
    for(int i=1;i<=4;i++)
    {
        for(int j=v[i];j<=m;j++)
        {
            f[j]+=f[j-v[i]];
        }
    }
    cout<<f[m]<<endl;
    return 0;
}

3.货币系统1

题目描述

给你一个n种面值的货币系统,求组成面值为m的货币有多少种方案。

输入格式
第一行,包含两个整数n和m。

接下来n行,每行包含一个整数,表示一种货币的面值。

输出格式
共一行,包含一个整数,表示方案数。

数据范围
n≤15,m≤3000
输入样例:

3 10
1
2
5

输出样例:

10

解题思路

完全背包问题问题求方案数,跟买书那道题几乎一样,完全背包问题从小到大枚举,每件物品有无数个面值为m的货币可以看做总体积,n种货币的面值看做体积

代码实现
#include<iostream>
#include<algorithm>
using namespace std;
const int N=30010;
typedef long long LL;
LL f[N];
int main()
{
    int n,m;
    cin>>n>>m;
    f[0]=1;
    for(int i=1;i<=n;i++)
    {
        int a;
        cin>>a;
        for(int j=a;j<=m;j++) f[j]+=f[j-a];
    }
    cout<<f[m]<<endl;
    return 0;
}

4.货币系统2

题目描述
解题思路

在这里插入图片描述
在这里插入图片描述如果其中某个数ai能被其他数表示出来,那一定是被比它小的数表示出来,将所有数从小到大排序,那么看ai能不能被a1~ai-1表示出来,如果能表示出来说明ai是多余的则一定不选,否则就成为新的货币系统的一个。

代码实现
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=110,M=25010;
int a[N],f[M];
int n,m;
int main()
{
    int t;
    cin>>t;
    while(t--)
    {
        cin>>n;
        for(int i=0;i<n;i++) cin>>a[i];
        sort(a,a+n);
        m=a[n-1];
        
        memset(f,0,sizeof f);
        f[0]=1;
        int res=0;
        for(int i=0;i<n;i++)
        {
            if(!f[a[i]]) res++;//如果a[i]不能被表示,则一定选,也就是表示a[i]的方案数是0
            for(int j=a[i];j<=m;j++)
            {
                f[j]+=f[j-a[i]];
            }
        }
        cout<<res<<endl;
    }
    
}

(3)多重背包(和完全背包不同的是每件物品的个数有限)

模型

暴力

当数据很小可以直接用暴力做法(只是比完全背包多了一个k<=s[i])

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

可以将多重背包拆成01背包

while(n--)
    {
    cin>>v>>w>>s;
    while(s--)
    {a[++t]=v;
    b[t]=w;}//死拆,把多重背包拆成01背包
    }
    for(int i=1;i<=t;i++)
    for(int j=m;j>=a[i];j--)
    dp[j]=max(dp[j-a[i]]+b[i],dp[j]);//直接套01背包的板子
    cout<<dp[m]<<endl;

优化

把一种物品拆分成分别为2的幂次的物品 利用二进制优化,时间复杂度就从O(n3)降到O(n2logS),从4*109降到了2*107。

for(int i=1;i<=n;i++)
    {
        cin>>v>>w>>s;
        for(int j=1;j<=s;j*=2)//打包每个物品
        {
            goods.push_back({j*v,j*w});
            s-=j;
        }
        if(s>0) goods.push_back({s*v,s*w});
    }
    for(auto good:goods)//01背包
    {
        for(int j=m;j>=good.first;j--)
        {
            f[j]=max(f[j],f[j-good.first]+good.second);
        }
    }

例题

1.多重背包问题

题目描述

有 N 种物品和一个容量是 V 的背包。

第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。

输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。

接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。

输出格式
输出一个整数,表示最大价值。

数据范围
0<N,V≤100
0<vi,wi,si≤100
输入样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2

输出样例:
10

解题思路

这就是多重背包的暴力解法的板子题,直接用模板即可

代码实现
#include<iostream>
#include<cstring>
using namespace std;
const int N=110;
int f[N][N];
int v[N],w[N],s[N];
int main()
{
    int n,V;
    cin>>n>>V;
    for(int i=1;i<=n;i++) cin>>v[i]>>w[i]>>s[i];
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<=V;j++)
        {
            for(int k=0;k<=s[i]&&k*v[i]<=j;k++)
            {
                f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+w[i]*k);
            }
        }
    }
    cout<<f[n][V]<<endl;
    return 0;
}

2.庆功会

题目描述

为了庆贺班级在校运动会上取得全校第一名成绩,班主任决定开一场庆功会,为此拨款购买奖品犒劳运动员。

期望拨款金额能购买最大价值的奖品,可以补充他们的精力和体力。

输入格式
第一行二个数n,m,其中n代表希望购买的奖品的种数,m表示拨款金额。

接下来n行,每行3个数,v、w、s,分别表示第I种奖品的价格、价值(价格与价值是不同的概念)和能购买的最大数量(买0件到s件均可)。

输出格式
一行:一个数,表示此次购买能获得的最大的价值(注意!不是价格)。

数据范围
n≤500,m≤6000,
v≤100,w≤1000,s≤10
输入样例:

5 1000
80 20 4
40 50 9
30 50 7
40 30 6
20 20 1

输出样例:

1040

解题思路

这道题直接用暴力的多重背包就能解题,也是直接用模板,将拨款金额看做体积即可

代码实现
#include<iostream>
#include<cstring>
using namespace std;
const int N=510,M=6010;
int f[N][M];
int v[N],w[N],s[N];
int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>v[i]>>w[i]>>s[i];
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<=m;j++)
        {
            for(int k=0;k<=s[i]&&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]<<endl;
    return 0;
}

(4)分组背包

模型

题目介绍

完全背包是从某种物品里选几个,分组背包问题是从每组里选哪个

假设选第k个,那集合就划分为不选第i组的第kf[i,j]=f[i-1,j]和选第i组的第kf[i,j]=max(f[i,j],f[i-v[ki]]+w[ki])
在这里插入图片描述

f[j]//总体积不大于j的最大价值
v[i][j]//第i组第j个物品的体积
w[i][j]//第i组第j个物品的价值
s[i]//第i组的物品数量
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]);
            }
        }
    }

例题

1.分组背包问题

题目描述

有 N 组物品和一个容量是 V 的背包。

每组物品有若干个,同一组内的物品最多只能选一个。
每件物品的体积是 vij,价值是 wij,其中 i 是组号,j 是组内编号。

求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。

输出最大价值。

输入格式
第一行有两个整数 N,V,用空格隔开,分别表示物品组数和背包容量。

接下来有 N 组数据:

每组数据第一行有一个整数 Si,表示第 i 个物品组的物品数量;
每组数据接下来有 Si 行,每行有两个整数 vij,wij,用空格隔开,分别表示第 i 个物品组的第 j 个物品的体积和价值;
输出格式
输出一个整数,表示最大价值。

数据范围
0<N,V≤100
0<Si≤100
0<vij,wij≤100
输入样例

3 5
2
1 2
2 4
1
3 4
1
4 5

输出样例:

8

解题思路

模板题

代码实现
#include<iostream>
using namespace std;
const int N=110;
int f[N],v[N][N],w[N][N],s[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]<<endl;
    return 0;
}

2.机器分配

题目描述

总公司拥有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

解题思路

将机器数量看做体积,每台机器的体积都是1
集合:给前i个公司分配机器,且总机器数不大于j的盈利
属性:最大值
状态计算:
1.不给第i个公司分配f[i,j]
2.给第i个公司分配,分配的可能是0台,1台…,f[i-1,j-k]+w[i][k]
方案与前面求方案数的方法是一样的,通过最大值求方案

代码实现
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=15,M=20;
int w[N][M];
int f[N][M];
int way[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=0;j<=m;j++)
        {
            for(int k=0;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;
}

3.金明的预算方案

题目描述

金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间金明自己专用的很宽敞的房间。

更让他高兴的是,妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过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

解题思路

对于每一个主件和附件,都可以看成一组,而且每一组,有n个附件就有n中选择,比如一个主件x,有两个附件uv,那么我们可以选择只购买主件x,或者主件x与附件u,或者主件x与附件v,或者主件x与附件u、附件v,每一种选择都对应一个决策,所以对于每一组,我们可以用枚举的方式,枚举每一个附件选与不选,然后取最大值,枚举采用二进制的形式,每一个附件对于二进制的一位,0代表不选,1代表选。

代码实现
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int M=32010,N=70;
typedef pair<int,int> PII;
#define v first
#define w second
int n,m;
PII master[N];
vector<PII> servent[N];
int f[M];
int main()
{
    cin>>m>>n;
    for(int i=1;i<=n;i++)
    {
        int v,w,q;
        cin>>v>>w>>q;
        if(!q) master[i]={v,v*w};
        else servent[q].push_back({v,v*w});
    }
    for(int i=1;i<=n;i++)
    {
        if(master[i].v)//如果物品价格是0,则买与不买都一样,所以只考虑价格不是0的
        {
            for(int j=m;j>=0;j--)
            {
                auto &sv=servent[i];
                for(int k=0;k<1<<sv.size();k++)//对于每一个附件,我们都可以选或者不选2种选择,所以有n的附件就有2的n次方个选择
                {
                    int v=master[i].v,w=master[i].w;
                    for(int u=0;u<sv.size();u++)//用二进制来表示每一位,二进制的1代表选,0代表不选
                    {
                        if(k>>u&1)
                        {
                            v+=sv[u].v;
                            w+=sv[u].w;
                        }
                    }
                    if(j>=v) f[j]=max(f[j],f[j-v]+w);
                }
            }
        }
    }
    cout<<f[m]<<endl;
}

4. 有依赖的背包问题

题目描述

有 N 个物品和一个容量是 V 的背包。

物品之间具有依赖关系,且依赖关系组成一棵树的形状。如果选择一个物品,则必须选择它的父节点。

如下图所示:
在这里插入图片描述
如果选择物品5,则必须选择物品1和2。这是因为2是5的父节点,1是2的父节点。

每件物品的编号是 i,体积是 vi,价值是 wi,依赖的父节点编号是 pi。物品的下标范围是 1…N。

求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。

输出最大价值。

输入格式
第一行有两个整数 N,V,用空格隔开,分别表示物品个数和背包容量。

接下来有 N 行数据,每行数据表示一个物品。
第 i 行有三个整数 vi,wi,pi,用空格隔开,分别表示物品的体积、价值和依赖的物品编号。
如果 pi=−1,表示根节点。 数据保证所有物品构成一棵树。

输出格式
输出一个整数,表示最大价值。

数据范围
1≤N,V≤100
1≤vi,wi≤100
父节点编号范围:

内部结点:1≤pi≤N;
根节点 pi=−1;
输入样例
5 7
2 3 -1
2 2 1
3 5 1
4 7 2
3 6 2
输出样例:
11

解题思路
代码实现

(5).混合背包问题

例题

1.混合背包问题

题目描述

有 N 种物品和一个容量是 V 的背包。

物品一共有三类:

第一类物品只能用1次(01背包);
第二类物品可以用无限次(完全背包);
第三类物品最多只能用 si 次(多重背包);
每种体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。

输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。

接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。

si=−1 表示第 i 种物品只能用1次;
si=0 表示第 i 种物品可以用无限次;
si>0 表示第 i 种物品可以使用 si 次;
输出格式
输出一个整数,表示最大价值。

数据范围
0<N,V≤1000
0<vi,wi≤1000
−1≤si≤1000
输入样例

4 5
1 2 -1
2 4 1
3 4 0
4 5 2

输出样例:

8

解题思路

每一件物品对最大价值的影响都跟前i-1件物品无关,我们只需要根据第i件物品能使用多少次来确定背包问题和状态转移方程,只能用一次——01背包,能用无数次——完全背包,每件物品有对次但数量有限——多重背包,因为数据较大,所以多重背包要用二进制优化。
在这里插入图片描述

代码实现
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010;
int f[N];
int n,m;
int main()
{
    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背包和多重背包 01背包其实就是特殊的多重背包
        {
            if(s==-1) s=1;
            for(int k=1;k<=s;k*=2)//打包每个物品
            {
                for(int j=m;j>=k*v;j--)
                {
                    f[j]=max(f[j],f[j-k*v]+k*w);
                }
                s-=k;
            }
            if(s)
            {
                for(int j=m;j>=s*v;j--) f[j]=max(f[j],f[j-s*v]+s*w);
            }
        }
    }
    cout<<f[m]<<endl;
    return 0;
}

01背包:不断对第 i个物品的做出决策,「0-1」正好代表不选与选两种决定。
完全背包:和01背包不同的是,01背包每件物品只有一个,而完全背包每件物品有无数个,只有完全背包的体积从小到大枚举
多重背包:和01背包不同的是,01背包每件物品只有一个,而多重背包每个物品有多个,但是数量有限,01背包是多重背包的特例
分组背包:完全背包是从某种物品里选几个,分组背包问题是从每组里选哪个
背包问题先枚举物品,再枚举体积,最后枚举数量,一维优化的时候,只有完全背包的体积从小到大枚举,其他背包的体积都是从大到小枚举

学习网站:ACwing
背包问题大都很明显,但有的需要从里面提取一些性质转化成01背包,如果是求最大价值,一般都是直接用模板,但是求方案数,记得对f数组初始化,将模板的max改为+=,争取多刷题多总结,加油!如果有错误或者不清楚的地方,欢迎指出!

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
背包问题可以使用动态规划和贪心算法进行求解。下面我会分别介绍这两种方法。 1. 动态规划(Dynamic Programming): 动态规划是一种将问题分解成更小的子问题并通过保存子问题的解来解决原始问题的方法。对于背包问题,可以使用动态规划来找到最优解。 具体步骤如下: - 定义一个二维数组dp,其中dp[i][j]表示在前i个物品中能够装入容量为j的背包中的最大价值。 - 初始化dp数组的第一行和第一列为0,表示没有物品或者背包容量为0时的最大价值都是0。 - 遍历物品,对于每个物品i,遍历背包容量j,进行判断: - 如果当前物品i的重量大于背包容量j,则该物品不能放入背包中,所以dp[i][j] = dp[i-1][j]。 - 如果当前物品i的重量小于等于背包容量j,则有两种情况: - 放入该物品后的总价值:dp[i][j] = dp[i-1][j-w[i]] + v[i],其中w[i]表示物品i的重量,v[i]表示物品i的价值。 - 不放入该物品后的总价值:dp[i][j] = dp[i-1][j]。 - 取上述两种情况的最大值作为dp[i][j]的值。 - 最终dp[n][m]即为背包问题的最优解,n表示物品的个数,m表示背包的容量。 2. 贪心算法: 贪心算法是一种每一步都选择当前状态下最优解的策略,但是不能保证获得全局最优解。对于背包问题,可以使用贪心算法来找到近似最优解。 具体步骤如下: - 首先计算每个物品的单位重量价值(价值除以重量),然后按照单位重量价值降序排列物品。 - 从排好序的物品中依次选择,将单位重量价值最高的物品放入背包中,直到背包无法容纳当前物品或者没有物品可选为止。 - 计算背包中物品的总价值,即为近似最优解。 需要注意的是,贪心算法并不能保证一定能得到最优解,只能得到一个近似最优解。而动态规划可以保证得到最优解。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值