动态规划-多重背包

今天是放寒假的重拾算法的第二天,渡过恐怖期末周后,这段期间都没怎么学习编程了,感觉自己又变成新兵蛋子了,趁寒假时间多,就多学学吧,这是寒假第一篇,争取寒假能写个十篇吧,话不多说上干货!!!

多重背包(Ⅰ)---无优化版本

以今天写的代码展开详细描述与解释,并附上题目

cpp 复制代码
const int N = 110;
int x[N], w[N], v[N];//个数,重量,价值
int f[N][N];

int main()
{
    int m, n;
    cin >> m >> n;
    for (int i = 1; i <= m; i++)
    {
        cin >> x[i] >> w[i] >> v[i];
    }
    for (int i = 1; i <= m; i++)//这个是种类
    {
        for (int j = n; j >= 0; j--)//这个是容量
        {
            for (int k = 0; k <= x[i] && k * w[i] <= j; k++)//k=0意为可以不选
            {
                f[i][j] = max(f[i][j], f[i - 1][j - k * w[i]] + k * v[i]);
            }
        }
    }
    cout << f[m][n] << endl;
    return 0;
}

这是一道在洛谷上写的题目,所以使用全局的变量更加方便,根据输入要求,我直接把数组设置的足够大,那么就不用考虑越界的问题

题目的主要实现逻辑在于三个for循环,理解每个循环的功能就能够理解其中的门道了

  • 数组x,w,v:分别存储的是每个种类的初始数据
  • 二维数组f:是用来进行动态规划的数组
  • 第一层for(i)循环:是遍历一遍所有的种类
  • 第二层for(j)循环:是遍历一次所有可能的容量

从右往左的原因:不同于完全背包,完全背包可以无限制重复选择,但是多重背包不行,因为多重背包每个物品并没有无限个,是有限的;如果是从左往右遍历,那么就变成了完全背包,不符合题意;所以,从右往左遍历,就可以不打破多重背包有数量限制的约束

  • 第三层for(k)循环:是遍历一次每个种类所可以选的数量,k需要满足条件(k <= x[i] && k * w[i] <= j),否则就不是符合条件的情况,这样可以直接剪枝,减少时间复杂度,k可以从0开始,因为可以选择不选
  • 最后输出的f[m][n]就是所需结果

多重背包(Ⅱ)---二进制优化版本

这个两个版本的题目都一样,就不重复展示了,这里直接展示代码

cpp 复制代码
const int N = 110 * 5;//一个物品最多20个,但是可以分成五堆
                      //(就是五种虚拟物品,并且有100个种类,最多需要的位置是500个位置)
                      //(这个是在加入x都是20的情况下)
int w[N], v[N], pos = 0;//重量,价值
int f[N];

int main()
{
    int n, T;
    cin >> n >> T;
    for (int i = 1; i <= n; i++)
    {
        int x, y, z;
        cin >> x >> y >> z;
        int t = 1;
        while (x >= t)
        {
            pos++;
            w[pos] = t * y;
            v[pos] = t * z;
            x -= t;
            t *= 2;
        }
        if (x)
        {
            pos++;
            w[pos] = x * y;
            v[pos] = x * z;
        }
    }
    //类似01背包,此时每个虚拟物品只有一个
    for (int i = 1; i <= pos; i++)//遍历每个虚拟物品的重量
    {
        for (int j = T; j >= w[i]; j--)//寻找符合条件的重量,并计算最大价值
        {
            f[j] = max(f[j], f[j - w[i]] + v[i]);
        }
    }
    cout << f[T] << endl;
    return 0;
}

题目的主要实现逻辑在于理解二进制优化,和模拟01背包动态规划

  • 此处我们要先理解这个原理---二进制优化

一个数例如7,我们二进制形式是0111,即1+2+4;那么在这里我们就可以把一个种类的数量划分成三堆,第一堆有一个,第二堆有两个,第三堆有四个;那么因为每一个的重量和价值都一样,那么我们可以把两个和四个的集合看成一个整体,设这个种类的重量为w,价值为v,那么两个为一个整体的话,它此时的重量为2w,质量为2v,以此类推;就是把每一个集合看成一个新的个体计算,减少了时间复杂度,可以转化成01背包问题;再有特殊,有余数的情况,例:20,它可以写成1+2+4+8+5;因为5没办法用单独一个2的次方表示,可以独立形成一个集合,再进行计算统计,🆗原理就是差不多这些,还不理解的话,可以尝试问问豆包举例举例,方便理解

  • 变量x,y,z:是题目的源数据
  • 变量pos:是用来统计把原来的种类转化成虚拟物品后有多少的个数统计
  • 常量N:是用来开数组空间的,为什么是550?因为题目说了,每个种类的物品最多有20个,然后20又最多划分成5堆,并且一共有100个种类,以防数组越界我初始化数组都是110个,所以最终由5*110个空间(理解了这里,差不多就知道,题目的实现逻辑了)
  • 第一个while循环:就是统计每个种类有几个集合,并且把每个集合对应的重量和价值存入w与v数组
  • 第二个while循环:在原理那一块,会有像20这种数字没办法完全变成由2的次数组成的数字,那么这个循环的作用就是统计余数集合的重量和价值
  • 双层for循环:就是模拟01背包问题,因为我们把源数据划分成了一个个的虚拟集合,相当于每个虚拟集合只有一个,就可以使用01背包的思维进行解答
  • 注:这个二进制优化不能用于解决求方案数问题,只能用于解决求最值问题

因为,我们集合里的物品都是同一种,例如9这个数字,可以分为1+2+4+2;如果我们 要找到符合重量为3的搭配方案(假设每个物品的重量为1),就可以把1和两个2分别搭 配,但是其实都是同一种情况,会重复统计

哇!酣畅淋漓的码字,希望读者可以看懂

多重背包(Ⅲ)---求方案数问题

以今天写的代码展开详细描述与解释,并附上题目

cpp 复制代码
const int N = 1e6 + 7;
const int M = 110;
int a[M];
int dp[M][M];

int main()
{
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
    }
    dp[0][0] = 1;
    for (int i = 1; i <= n; i++)
    {
        for (int j = m; j >= 0; j--)
        {
            for (int k = 0; k <= a[i] && k <= j; k++)
            {
                dp[i][j] = (dp[i][j] + dp[i - 1][j - k]) % N;
            }
        }
    }
    cout << dp[n][m] << endl;
    return 0;
}

这是一道在洛谷上写的题目,所以使用全局的变量更加方便,根据输入要求,我直接把数组设置的足够大,那么就不用考虑越界的问题

题目的主要实现逻辑在于三个for循环,理解每个循环的功能就能够理解其中的门道了

  • 数组a:用于存储标记每个种类不能摆放超过的数量
  • 第一层for(i)循环:遍历一遍所有的种类
  • 第二层for(j)循环:遍历一遍所有可能容量数
  • 第三层for(k)循环:遍历这个种类所拥有的个数,但是k要满足条件(k <= a[i] && k <= j),否则是不符合标准的;因为方案数可能很大,在每次计算时,我们要边求和,边取模,就可以保证最终的结果准确
  • 最终返回dp[n][m]的数值

优化版本

cpp 复制代码
const int N = 1e6 + 7;
const int M = 110;
int a[M];
int dp[M];

int main()
{
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
    }
    dp[0] = 1;
    for (int i = 1; i <= n; i++)
    {
        for (int j = m; j >= 0; j--)
        {
            for (int k = 1; k <= a[i] && k <= j; k++)
            {
                dp[j] = (dp[j] + dp[j - k]) % N;
            }
        }
    }
    cout << dp[m] << endl;
    return 0;
}

这个是空间优化的版本

空间优化时,我们不仅仅要修改数组的维数,还要更改k的起始遍历值,若k还是从0开始遍历,那么就是导致重复统计dp[j]的情况,导致结果错误

相关推荐
C+++Python2 小时前
序列式容器 - list
数据结构·windows·list
ZPC82102 小时前
opencv 实现图像拼接
人工智能·python·算法·机器人
爱学习的阿磊2 小时前
C++代码动态分析
开发语言·c++·算法
WWZZ20252 小时前
C++:STL(容器deque)
开发语言·c++·算法·大模型·具身智能
AI科技星2 小时前
加速运动正电荷产生加速度反向引力场的详细求导过程
人工智能·线性代数·算法·机器学习·矩阵·概率论
近津薪荼2 小时前
优选算法——双指针专题3(快慢双指针)
c++·学习·算法
shengli7222 小时前
C++与硬件交互编程
开发语言·c++·算法
tobias.b10 小时前
408真题解析-2010-6-数据结构-哈夫曼树
数据结构·计算机考研·408真题解析
tobias.b11 小时前
408真题解析-2010-7-数据结构-无向连通图
数据结构·算法·图论·计算机考研·408真题解析