博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
动态规划——背包问题汇总
阅读量:4521 次
发布时间:2019-06-08

本文共 3392 字,大约阅读时间需要 11 分钟。

一.0/1背包

  题目链接:

  因为二维数组的动规维护极其简单,这里就不再详述了。

  二维数组降低空间开销的方法是使用滚动数组,可以将空间复杂度从O(nm)降低为O(m),此处也不赘述。

  直接讲讲一维数组维护的思路:

  先看二维数组动规的状态转移方程:

  F[i,j]=max{F[i-1,j],F[i-1,j-Vi]+Wi(if j>=Vi)}

  边界:F[0,0]=0;目标:F[n][m]

  我们发现,对于每一层i,数组的维护过程中仅是j的值在改变,故可以直接将数组的第一维省略。

  那么状态转移方程就变成:F[j]=max{F[j-Vi]+Wi(if j>=Vi)}

  但这里有一个注意点,就是循环的顺序问题。第一层i的1~n循环自然没问题,主要是第二层的j,必须是采用倒序循环m~V[i]。

  原因是F[j]的值是与F[j-Vi]有关的,若采用正序循环,那么在更新到这一层的F[j]时,所关心的F[j-Vi]已经是这一层的了,而不是上面二维中所需的第i-1层(也就是上一层)了。

  代码实现如下:时间复杂度O(nm)

  事先说明一下:本文所有代码中的读取均使用快读,下面先贴一下快读代码:

inline int read(){    int x=0,f=1;char ch=getchar();    while(ch<'0'||ch>'9'){
if(ch=='-')f=-1;ch=getchar();} while(ch<='9'&&ch>='0')x=(x<<1)+(x<<3)+(ch^48),ch=getchar(); return x*f;}int zero_one_knapsack() // 0/1背包{ int const N = 1000+10; int v[N],w[N],f[N]; memset(f,0,sizeof(f)); int n=read(),m=read(); for(int i=1;i<=n;i++)v[i]=read(),w[i]=read(); 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]); return f[m];}

 

二.完全背包

  题目链接:

  类似于0/1背包,当采用正序循环时,每种物品就可以使用无限次了。

  话不多说,直接上代码:

int full_knapsack()  // 完全背包{    int const N = 1000+10;    int v[N],w[N],f[N];    memset(f,0,sizeof(f));        int n=read(),m=read();    for(int i=1;i<=n;i++)v[i]=read(),w[i]=read();        for(int i=1;i<=n;i++)        for(int j=v[i];j<=m;j++)            f[j]=max(f[j],f[j-v[i]]+w[i]);            return f[m];}

 

三.多重背包

  题目链接:

  最直接的方法是把第i种物品看成独立的Si个物品,转化为共有ΣCi(i=1~n)个物品的0/1背包问题进行计算,时间复杂度O(M*ΣCi)。

  这种方法把每种物品拆成了Si个,效率较低。

  代码如下:

int multiple_knapsack_i()  // 多重背包I{    int const N = 1000+10;    int v[N],w[N],s[N],f[N];    memset(f,0,sizeof(f));        int n=read(),m=read();    for(int i=1;i<=n;i++)v[i]=read(),w[i]=read(),s[i]=read();        for(int i=1;i<=n;i++)        for(int j=1;j<=s[i];j++)            for(int k=m;k>=v[i];k--)                f[k]=max(f[k],f[k-v[i]]+w[i]);                return f[m];}

  下面介绍二进制优化算法:

  众所周知,任何整数都可以表示为若干个2的整数次幂相加。所以我们可以将每种物品的Si个打包成若干组,每一组的数量取值为:1,2,4,8,…,2k-1,sum;sum的作用是使得这组数的和的最大值恰好为Si。

  那么就可以把每种物品分成log2Si个,效率较高。

  代码如下:

int multiple_knapsack_ii()  // 多重背包II  二进制优化{    int const N = 1010*11, M = 2010;    int v[N],w[N],f[M];    memset(f,0,sizeof(f));        int n=read(),m=read();    int cnt=0;    for(int i=1;i<=n;i++)    {        int a=read(),b=read(),s=read(),k=1;        while(k<=s)        {            v[++cnt]=a*k;            w[cnt]=b*k;            s-=k;k<<=1;        }        if(s)        {            v[++cnt]=a*s;            w[cnt]=b*s;        }    }    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]);            return f[m];}

 

四.分组背包

  题目链接:  

  有了上面几个背包模型,分组背包也是极其简单,但有几个注意点。

  首先是倒序循环,其次,对于每一组内Si个物品的枚举,k应放在j的内层,这是因为每一组内至多选择一个物品,若把k至于j的外层,就会在F数组的转移上产生累积,最终会选择超过1个物品。

  从动规的角度看,i是“阶段”,i与j共同构成“状态”,而k是“决策”,在第i组内选择哪个物品,这三者的顺序绝对不能混淆。

  代码如下:

int grouping_knapsack(){    int const N = 1000+10;    int v[N][N],w[N][N],s[N],f[N];    memset(f,0,sizeof(f));        int n=read(),m=read();    for(int i=1;i<=n;i++)    {        s[i]=read();        for(int j=1;j<=s[i];j++)            v[i][j]=read(),w[i][j]=read();    }    for(int i=1;i<=n;i++)        for(int j=m;j>=0;j--)            for(int k=1;k<=s[i];k++)                if(j>=v[i][k])f[j]=max(f[j],f[j-v[i][k]]+w[i][k]);                return f[m];}

 

  最后说一句,动规思想解决背包问题最需要注意的就是循环的嵌套,必须要以物品->体积->决策的顺序。

转载于:https://www.cnblogs.com/ninedream/p/11191648.html

你可能感兴趣的文章
【剑指Offer】21栈的压入、弹出序列
查看>>
【剑指Offer】46孩子们的游戏(圆圈中最后剩下的数)
查看>>
PHP正则表达式
查看>>
Forbidden (403) CSRF verification failed. Request aborted.
查看>>
获取spring applicationcontext
查看>>
基础算法--归并排序
查看>>
Android Client 与 C# Server 的Socket通信
查看>>
Android一句话备忘录
查看>>
java第二次作业
查看>>
Unity_Shader开发_认识(一)
查看>>
求一维循环数组最大子数组
查看>>
vue中超简单的方法实现点击一个按钮出现弹框,点击弹框外关闭弹框
查看>>
Iterator作用
查看>>
【NOIP2005】【Luogu1046】陶陶摘苹果
查看>>
2018 “百度之星”程序设计大赛 - 初赛(A)P1001度度熊拼三角(贪心)
查看>>
算法(2)--排序算法
查看>>
分布式与集群理解之部署结构
查看>>
sublime text3 -- JavaScript Completions
查看>>
二、Django需要的知识点
查看>>
PCB新手值得一看《Protel使用中的问题》
查看>>