动态规划——dp思想总结

动态规划——dp思想总结

DP思想:常用于解决在一个有限集当中,时间复杂度特别大,求其中的最大值、最小值、某个数的个数等,可以用DP方法来解决。
解题一般分两个步骤:1.状态表示f(v,j);2.状态计算。
两大准则:不重复,不遗漏。
划分依据:寻找最后一个不同点。

题型一:01背包
题目链接
题型是有限物品有限容量求最大值问题。
思路分析:
则有一个物品要么选要么不选,只有两种情况,所以总共的方案数有2的N次方个;也就是一个有限集合求最值问题。
第一步:状态表示f(v,j)
f(v,j):第一维(v)考虑前i个物品;其他维(第二第三维):对选择过程中加上限制(例如体积的限制,重量的限制)。
第二步:状态计算
计算分两个方向:就对与一个物品来说,它只有选与不选,则每当到一个物品面前时,就会分成两个方案:1.所有选择该物品(i)的方案;2.所有不选择该物品(i)的方案。
通过以上两个步骤可得:f(i,j)=max(f(i-1,j),f(i-1,j-vi)+wi)
for循环:for(j=v;j>=vi,j–)
代码一:二维数组

//01背包
#include <iostream>
using namespace std;
const int N=1010;
int n,m;//n表示物品数量,m表示背包的最大体积
int v[N],w[N];//v[N]表示体积,w[N]表示价值
int f[N][N];//进行动态规划的数组
int main()
{
    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=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]);
        }
    }
    cout<<f[n][m]<<endl;
    return 0;
}

代码二:优化后变成一维数组

//01背包
#include <iostream>
using namespace std;
const int N=1010;
int n,m;//n表示物品数量,m表示背包的最大体积
int v[N],w[N];//v[N]表示体积,w[N]表示价值
int f[N];//进行动态规划的数组
int main()
{
    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>=0;j--)
        {
            //f[j]=f[j];
            //f[i][j]=f[i-1][j];//左半边的子集


            if(j>=v[i])//当前背包容量应大于当前选择物品的容量
                f[j]=max(f[j],f[j-v[i]]+w[i]);
                //f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
        }
    }
    cout<<f[m]<<endl;
    return 0;
}
/*
优化过后可看出来j循环变成了从大到小循环,因为数组变成了一维数组
,删除了i的部分,f[j]=f[j]等价于f[i][j]=f[i-1][j];
f[j]=max(f[j],f[j-v[i]]+w[i])等价于f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);

*/

到这里停一下,来总结一下优化的依据是什么:
就是一定要保证优化前后的核心代码如f[j]=max(f[j],f[j-v[i]]+w[i])一定要是等价的!一定要是等价的!!!

题型二:完全背包
题目链接

先说一下结论:01背包优化后的代码和完全背包是相似的,但是其中的推导过程大不相同。
把01背包中j的从大到小循环再修改为从小到大循环就可以解决完全背包问题。
如代码一:

//01背包
#include <iostream>
using namespace std;
const int N=1010;
int n,m;//n表示物品数量,m表示背包的最大体积
int v[N],w[N];//v[N]表示体积,w[N]表示价值
int f[N];//进行动态规划的数组
int main()
{
    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=v[i];j<=m;j++)
        {
            //f[j]=f[j];
            //f[i][j]=f[i-1][j];//左半边的子集


            //当前背包容量应大于当前选择物品的容量
            f[j]=max(f[j],f[j-v[i]]+w[i]);
                //f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
        }
    }
    cout<<f[m]<<endl;
    return 0;
}
/*
优化过后可看出来j循环变成了从大到小循环,因为数组变成了一维数组
,删除了i的部分,f[j]=f[j]等价于f[i][j]=f[i-1][j];
f[j]=max(f[j],f[j-v[i]]+w[i])等价于f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);

*/

完全背包中一个物品可以重复选,所以对于一个物品来说,有两种情况,不选该物品,或者该物品选k个。
状态转移方程为:f(i,j)=max(f(i-1,j),f(i,j-v)+w);


先在这里总结一下01背包和完全背包中状态转移方程的区别:
01背包:f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]);
完全背包:f[i][j]=max(f[i-1][j],f[i][j-v[i]]+w[i]);
由此可以清楚的看出来两方程的区别,由于这个区别而造成01背包中j是从大到小,而完全背包中j是从小到大。
代码二:(普通方法)

//01背包
#include <iostream>
using namespace std;
const int N=1010;
int n,m;//n表示物品数量,m表示背包的最大体积
int v[N],w[N];//v[N]表示体积,w[N]表示价值
int f[N][N];//进行动态规划的数组
int main()
{
    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=1;j<=m;j++)
        {
            //f[j]=f[j];
            //f[i][j]=f[i-1][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]);
        }
    }
    cout<<f[n][m]<<endl;
    return 0;
}
/*
优化过后可看出来j循环变成了从大到小循环,因为数组变成了一维数组
,删除了i的部分,f[j]=f[j]等价于f[i][j]=f[i-1][j];
f[j]=max(f[j],f[j-v[i]]+w[i])等价于f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);

*/

此时,我们再回顾一下完全背包的代码一,可知为什么j要从小到大了。
在这里带大家具体总结一下j循环方向的原因吧:
先说一下省略一个维度的思想,省略的那一维度是i,而i跑到哪了呢?i是通过第一个for循环来控制的,所以当用一维数组时,for循环中i是几就是对应中二维数组中的i。
在01背包中:
变成一维数组时:f[j]=max(f[j],f[j-v[i]]+w[i]);该方程省略了i-1这个部分,所以应让j从大到小循环,这样就有:先假设上一层设定为i,那该层中就代表的是i-1。
在完全背包中:
变成一维数组时,f[j]=max(f[j],f[j-v[i]]+w[i]);该方程省略了i这个部分,所以应让j从小到大循环,这样就有:先假设上一层设定为i-1,那么该层中就代表的是i。
哈哈哈,终于弄懂了j从大到小或者从小到大的原因了。
在这里说一个通过上述总结出来的顺口溜吧:
j的循环条件,从m开始(01),在m结束(完全)。

————————————————————————————
接下来,我们该接触另一种dp题型
石子合并:
题目链接

首先,进行集合表示:
将(i,j)区间合并成一堆的方案的集合。
求最小值。
代码一:

//石子合并
#include <iostream>
using namespace std;
const int N=310;
int n;
int s[N];
int f[N][N];
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>s[i];
        s[i]+=s[i-1];
    }
    for(int len=2;len<=n;len++)
    {
        for(int i=1;i+len-1<=n;i++)
        {
            int j=i+len-1;
            f[i][j]=1e8;
            for(int k=i;k<j;k++)
            {
                f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+s[j]-s[i-1]);
            }
        }
    }
    cout<<f[1][n]<<endl;
    return 0;
}

最长公共子序列
题目链接

首先,进行集合表示:
所有A(1—i)与B(1—j)的公共子序列的集合。
求最大值。
代码一:

//石子合并
#include <iostream>
using namespace std;
const int N=1010;
int n,m;
char a[N],b[N];
int f[N][N];
int main()
{
    cin>>n>>m>>a+1>>b+1;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            f[i][j]=max(f[i-1][j],f[i][j-1]);
            if(a[i]==b[j])
                f[i][j]=max(f[i][j],f[i-1][j-1]+1);
        }
    }
    cout<<f[n][m]<<endl;
    return 0;
}
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值