动态规划入门(五)

来源:互联网 发布:淘宝店铺地址在哪里看 编辑:程序博客网 时间:2024/06/10 01:28

今天,我们初步接触背包问题,主要针对于背包中的完全背包和01背包的原理及拓展进行讲述。


1.01背包
01背包指在一个有n件物品,一个容积为m的背包这样一个情况下,使能装下的物品价值最高。(w[i]表示重量,c[i]表示价值)
拿到这一题,首先想是否满足于DP的原理(因为求最优值),那么一看,先列出一个阶段n,表示前n件物品,状态j表示装的物品总容积为j,那么可设:f[i][j]——表示前i件物品构成j的体积的最大价值,那么如何写状态转移方程呢?因为是01背包,所以可以取或不取,那么就分成了两种情况:
①如果j>=w[i],那么f[i][j]=max(f[i-1][j],f[i-1][j-a[i]]+c[i]);//这是可以取的情况。
②如果j小于w[i],那么f[i][j]=f[i-1][j];//这是取不了的情况。
结果就出来了,那么我们可以对应写出程序:

#include<stdio.h>#include<stdlib.h>#include<iostream>using namespace std;int w[1001],c[1001];int f[1001][1001];int main(){    int i,j,k,n,m;    cin>>m>>n;    for(i=1;i<=n;i++)        cin>>w[i]>>c[i];    for(i=1;i<=n;i++)        for(j=m;j>0;j--)            if(j>=w[i])                f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+c[i]);            else f[i][j]=f[i-1][j];    printf("%d\n",f[n][m]);    return 0;}

拓展:01背包的优化:
我们要想到,因为这个算法是O(n*m)的,空间容量也是如此,那么一旦m比较大,那会爆空间啊!怎么办呢?这使我们要用到一个神奇的数组——滚动数组。
我们如果用f[j]表示不超过j公斤的最大价值,其他变量不变,那么状态转移方程怎么写呢?
f[j]=max(f[j],f[j-a[i]]+c[i]);
这样就能够优化空间了,具体看程序:

#include<stdio.h>#include<stdlib.h>#include<iostream>using namespace std;int w[20001],c[20001],f[20001];int main(){    int i,j,k,n,m;    cin>>m>>n;    for(i=1;i<=n;i++)        cin>>w[i]>>c[i];    for(i=1;i<=n;i++)        for(j=m;j>=w[i];j--)            f[j]=max(f[j],f[j-w[i]]+c[i]);    cout<<f[m]<<endl;    return 0;}

2.完全背包
完全背包不同于01背包的,在于完全背包有n种物品,每种有无限个,那么我们还是按照01背包的思路想,设f[i][j]表示前i种取出重量不超过j的最大价值,那么状态转移方程就出来了(因为每一件物品虽然可以取无数件,但是因为不能超过上限,所以最多取m/w[i]件):
f[i][j]=max(f[i-1][j],f[i-1][j-k*w[i]]+k*c[i]);
程序如下:

#include<stdio.h>#include<stdlib.h>#include<iostream>using namespace std;int w[2001],c[2001],f[2001][2001];int main(){    int i,j,k,n,m;    cin>>n>>m;    for(i=1;i<=n;i++)        cin>>w[i]>>c[i];    for(i=1;i<=n;i++)        for(j=m;j>=1;j--)            for(k=0;k<=m/w[i];k++)                if(k*w[i]<=j)f[i][j]=max(f[i][j],f[i-1][j-w[i]*k]+c[i]*k);    cout<<f[n][m]<<endl;    return 0;}

那么,有没有更方便的呢?我们能不能优化时间复杂度呢?答案是肯定的,我们想想,01背包的j从后往前推是为了不让每一个物件多取,而完全背包正是需要让每一个物件多取,所以我们完全可以把j的循环顺序倒过来,从0~v,那么看程序:

#include<stdio.h>#include<stdlib.h>#include<iostream>using namespace std;int w[2001],c[2001],f[1001][1001];int main(){    int i,j,k,n,m;    cin>>m>>n;    for(i=1;i<=n;i++)        cin>>w[i]>>c[i];    for(i=1;i<=n;i++)        for(j=1;j<=m;j++)            if(j>=w[i])f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+c[i]);            else f[i][j]=f[i-1][j];    cout<<f[n][m]<<endl;    return 0;}

这时,我们就转换到了一个01背包,那么空间还能不能优化呢?再一次掌声请出滚动数组,程序如下:

#include<stdio.h>#include<stdlib.h>#include<iostream>using namespace std;int w[2001],c[2001],f[2001];int main(){    int i,j,k,n,m;    cin>>m>>n;    for(i=1;i<=n;i++)        cin>>w[i]>>c[i];    for(i=1;i<=n;i++)        for(j=1;j<=m;j++)            if(j>=w[i])f[j]=max(f[j],f[j-w[i]]+c[i]);    cout<<f[m]<<endl;    return 0;}

拓展:完全背包用二进制转化成01背包
我们在上文讲过,因为完全背包中的每一件物品最多取m/w[i]个,那么我们可以把他们按照二进制分解成为01背包,他们可以按照二进制的系数分组,如:1,2,4……,如果不够再分,那么就剩下的就自成一组!具体见代码:

#include<stdio.h>#include<stdlib.h>#include<iostream>using namespace std;int w[100001],c[100001],f[100001];int main(){    int i,j,k,n,m,a,b;    cin>>m>>n;    int n1=0;    for(i=1;i<=n;i++){        cin>>a>>b;        k=m/b;        int t=1;        while(k>=t){            w[++n1]=a*t;c[n1]=b*t;            k-=t;t*=2;        }        c[++n1]=b*k;w[n1]=a*k;    }     for(i=1;i<=n1;i++)        for(j=m;j>=w[i];j--)            f[j]=max(f[j],f[j-w[i]]+c[i]);    cout<<f[m]<<endl;    return 0;}

推荐几篇动态规划的博客
六,五,四,三,二,一

1 0
原创粉丝点击