DP问题从入门到精通1(背包问题)

Posted 芜湖之肌肉金轮

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了DP问题从入门到精通1(背包问题)相关的知识,希望对你有一定的参考价值。

最近写了很多dp的题目,状态方程只要转换出来,就特别简单,所以特意去研究了一下,怎么去做dp的题目,一下是一些见解。

背包问题

背包问题是dp问题的常客了,一般来说只要能将状态方程转换出来,就很好写。那么怎么样才能很快速的写出状态方程呢?emmm貌似没有什么好办法哈哈,多写就好了,只要把dp的题目类型都见一遍,那么状态方程就不是什么难事了。

01背包


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

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

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


上面这个就是很经典的01背包,那么首先状态方程是啥,状态方程其实就是我们怎么看他,怎么去分析他的过程,这个二维的状态方程事f[i][j],意思是从前i个物品中选,体积不超过j的价值,根据题目意思,故该方程的属性事max,所以是从前i个物品中选,体积不超过j的最大价值。

 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]);
        }

 完全背包


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

第 ii 种物品的体积是 vivi,价值是 wiwi。

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


   for(int i=1;i<=n;i++)
        for(int j=1;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]);
        }

 我们可以发现完全背包问题和01背包问题代码只相差一点点一个是max:f[i-1][j-v[i]]+w[i],一个是max:f[i][j-v[i]]+w[i],为什么呢,这里面其实有个转换。

这个图是从acwing上找的:完全背包(顺便y总yyds)。通过这个转换,我们就很清晰的知道了为什么是f[i][j-v[i]]+w[i]。然后根据我们的状态表示从前i个物品中选,体积不超过j的最大价值,故最后答案就是f[n][m](假设n个物体,最大体积是m)

多重背包

多重背包的题目如果数据较小是可以直接o(n^3)暴力去求解的,状态表示还依旧是从前i个物品中选,体积不超过j的最大价值。所以状态方程就是f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);

   for(int i=1;i<=n;i++)//枚举所有物品
        for(int j=0;j<=m;j++)
        {
            f[i][j]=f[i-1][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]);
            }
        }

但是这个是暴力的解法,假如数据很大呢?比如数据到了1000?,那么10^9肯定会超时。这时候我们可能回想能不能优化成01背包?把每一个三份物体全部看作是三个一份的物体,然后用01背包的思路去做是不是更好?但是一个一个的分实在是太慢了,除了一份一份的分从达到表达原来物体份数的方法,还有没有别的更好的呢?这个时候就要用到背包问题的二进制优化方法:


比如随便一个数字:15,我们可不可以用最少的数字去表示,答案是可以找到的,我们可以用1,2,4,8去表示从0~15所有的数:0一个都不选,1选1,2选2,3选1+2,4选4,5选......14选2+4+8,15选1+2+4+8。如果折射到一般情况的话就是:

        
int n;//定义任意一个自然数
vector<int> f
cin>>n;
int N=n;
for(int j=1;j<=n;j*=2)
{
    n-=j;
    f.push_back(j)       
}

if(n>0)f.push_back(n);//假如s大于0说明,说明现在数组的数可以组成N-n的整数,所以要加上剩下的n
     

 这样我们就把拆分成份的时间复杂度优化成了o(logn)。


我们已经通过二进制把物体都拆成了只能选一次的各个子物体,这样子就可以直接用01背包去做就可以了

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

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

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

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

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

输出格式
输出一个整数,表示最大价值。
*/
const int N = 2010;
vector<pair<int,int >> vw;
int n,m;
int v,w,s;
int f[N];
int main()
{
    cin>>n>>m;
    for(int i=0;i<n;i++)
    {
        cin>>v>>w>>s;
        for(int j=1;j<=s;j*=2)
        {
            s-=j;
            vw.push_back({j*v,j*w});
        }
        if(s>0)vw.push_back({s*v,s*w});
    }
   
    for(int i=1;i<=vw.size();i++)
    {
        for(int j=m;j>=0;j--)
        {
            auto t=vw[i-1];
            f[j]=f[j];
            if(j>=t.first)f[j]=max(f[j],f[j-t.first]+t.second);
        }
    }
        cout<<f[m];
    return 0;
}

 

分组背包

 


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

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

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

输出最大价值


 经过以上的各种背包问题的洗礼,这个就比较容易想到这个的状态表示方程(才不是因为每个背包问题的状态表示都一样...)从前i(注意这里是不同的这里是“”)物品中选,体积不超过j的最大价值。

剩下的就和之前01背包一样了:

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

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

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

输出最大价值。

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

接下来有 N 组数据:

每组数据第一行有一个整数 Si,表示第 i 个物品组的物品数量;
每组数据接下来有 Si 行,每行有两个整数 vij,wij,用空格隔开,分别表示第 i 个物品组的第 j 个物品的体积和价值;
输出格式
输出一个整数,表示最大价值。
*/
const int N = 110;
int f[N];
int v[N][N],w[N][N],s[N];
int n,m;
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=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]);
            }
    
    cout<<f[m];
    return 0;
}

 背包类dp结束

(以上就是本菜鸡学习下来的一点点心得体会吧,希望能帮到大家---y总的思路是真的好www)

学习网站acwing

以上是关于DP问题从入门到精通1(背包问题)的主要内容,如果未能解决你的问题,请参考以下文章

DP问题从入门到精通2.2(线性DP,最短编辑距离)

DP问题从入门到精通2.2(线性DP,最短编辑距离)

DP问题从入门到精通2.2(线性DP,最短编辑距离)

DP问题从入门到精通3(区间DP,计数DP)

DP问题从入门到精通3(区间DP,计数DP)

DP问题从入门到精通3(区间DP,计数DP)