【一周编程学习】--7.DP动态规划 0-1背包问题

1.01背包问题

0-1 背包问题:给定 n 种物品和一个容量为 V的背包,物品 i 的重量是 wi,其价值为 vi 。
问:应该如何选择装入背包的物品,使得装入背包中的物品的总价值最大?

首先回顾动态规划的内容,DP与分治区别在于划分的子问题是有重叠的,解过程中对于重叠的部分只要求解一次,记录下结果,其他子问题直接使用即可,减少了重复计算过程。

  • 最优子结构性质:最优解包含了其子问题的最优解,不是合并所有子问题的解,而是找最优的一条解线路,选择部分子最优解来达到最终的最优解。
  • 子问题重叠性质:先计算子问题的解,再由子问题的解去构造问题的解(由于子问题存在重叠,把子问题解记录下来为下一步使用,这样就直接可以从备忘录中读取)。其中备忘录中先记录初始状态。

(1)求解思路

  1. 将原问题分解为子问题(子问题和原问题形式相同,且子问题解求出就会被存);
  2. 确定状态:01背包中一个状态就是N个物体中第i个是否放入体积为V背包中;
  3. 确定一些初始状态(边界状态)的值;
  4. 确定状态转移方程,如何从一个或多个已知状态求出另一个未知状态的值。(递推型)

(1). 确认子问题和状态
  01背包问题需要求解的就是,为了体积V的背包中物体总价值最大化,N件物品中第i件应该放入背包中吗?(其中每个物品最多只能放一件)
  为此,我们定义一个二维数组,其中每个元素代表一个状态,即前i个物体中若干个放入体积为V背包中最大价值。数组为:m[N][V],其中m[i][j]表示前i件中若干个物品放入体积为j的背包中的最大价值。
(2)初始状态
  初始状态为m[0][0−V]和m[0−N][0]都为0,前者表示前0个物品(也就是空物品)无论装入多大的包中总价值都为0,后者表示体积为0的背包啥价值的物品都装不进去。
(3)转移函数

if(j>w[i])
m[i][j]=max(m[i-1][j],m[i-1][j-w[i]]+v[i]);
else
m[i][j]=m[i-1][j];

最后一句的意思就是根据“为了体积V的背包中物体总价值最大化,N件物品中第i件应该放入背包中吗?”转化而来的。wi表示第i件物体的体积,vi表示第ii件物品的价值。这样m[i-1][j]代表的就是不将这件物品放入背包,而m[i-1][j-w[i]]+v[i]则是代表将第i件放入背包之后的总价值,比较两者的价值,得出最大的价值存入现在的背包之中。
(4)程序

#include<iostream>
using namespace std;

const int n =10;
int main()
{
	int narr[6][13]={{0}};
	int nval[6]={0,2,5,3,10,4};
	int nvol[6]={0,1,3,2,6,2};
	int bagv=12;
	for(int i=1;i<sizeof(nval)/sizeof(int);i++){
		for(int j=1;j<=bagv;j++)
		{
			if(j>=nvol[i])
				narr[i][j]=max(narr[i-1][j],narr[i-1][j-nvol[i]]+nval[i]);
			else
				narr[i][j]=narr[i-1][j];
			cout<<narr[i][j]<<" ";
		}
		cout<<endl;
	}
	cout<<narr[5][12]<<endl;
	return 0;
}

在这里插入图片描述

某工厂预计明年有A、B、C、D四个新建项目,每个项目的投资额Wk及其投资后的收益Vk如下表所示,投资总额为30万元,如何选择项目才能使总收益最大?

#include <iostream>
#include <cstring>
using namespace std;
 
const int N=150;
 
int v[N]={0,12,8,9,5};
int w[N]={0,15,10,12,8};
int x[N];
int m[N][N];
int c=30;
int n=4;
void traceback()
{
    for(int i=n;i>1;i--)
    {
        if(m[i][c]==m[i-1][c])
            x[i]=0;
        else
        {
            x[i]=1;
            c-=w[i];
        }
    }
    x[1]=(m[1][c]>0)?1:0;
}
 
int main()
{
 
 
    memset(m,0,sizeof(m));
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=c;j++)
        {
            if(j>=w[i])
                m[i][j]=max(m[i-1][j],m[i-1][j-w[i]]+v[i]);
 
            else
                m[i][j]=m[i-1][j];
        }
    }/*
    for(int i=1;i<=6;i++)
    {
        for(int j=1;j<=c;j++)
        {
            cout<<m[i][j]<<' ';
        }
        cout<<endl;
    }
*/
    traceback();
    for(int i=1;i<=n;i++)
        cout<<x[i];
    return 0;
}

输出x[i]数组:0111,输出m[4][30]:22。

2.Leetcode 第132题 基于动态规划DP、c++解法

在这里插入图片描述

输入一个字符串,将其进行分割,分割后各个子串必须是“回文”结构,要求最少的分割次数。显然,为了求取最少分割次数,一个简单的思路是穷尽所有分割情况,再从中找出分割后可构成回文子串且次数最少的分割方法。

解题思路:
对于一个字符串,我们需要考虑所有可能的分割,这个问题可以抽象成一个DP问题,对于一个长度为n的字符串,设DP[i][j]表示第i个字符到第j个字符是否构成回文,若是,则DP[i][j]=1;若否,则DP[i][j]=0;如此,根据回文的约束条件(对称性),DP[i][j]构成回文需满足:

  • 1、输入字符串s[i]==s[j],对称性;

  • 2、条件1满足并不能保证i到j构成回文,还须:(j-i)<=1或者DP[i+1][j-1]=1;即,i、j相邻或者i=j,也就是相邻字符相等构成回文或者字符自身构成回文,如果i、j不相邻或者相等,i到j构成回文的前提就是DP[i+1][j-1]=1.

所以状态转移方程:DP[i][j]=(s[i]==s[j]&&(j-i<=1||DP[i+1][j-1]==1))。由于i必须小于j,i>=0&&i<s.length().如此,DP[i][j]构成一个下三角矩阵,空间、时间复杂度均为O(n2)

比如:s=‘baab’ 回文串有:b a a b aa baab

当输入字符串所有可能的分割情况求出来之后,我们需要进一步寻找最少分割次数,我们可以用一个一维数组来存储分割次数vector cut(s.size()+1,0);:设表示第i个字符到最后一个字符所构成的子串的最小分割次数,这里的i有约束条件,就是第i个位置必须是可进行回文分割的,即DP[i][j]==1 (j>=i&&j<s.size()),故 cut[i]=min(1+cut[j+1],cut[i]);

使用C++实现:

class Solution {
public:
    int minCut(string s) 
    {
        vector<vector<int>> dp;
        vector<int> temp;
        for(int i=0;i<s.size();i++)
        temp.push_back(0);
        
        for(int i=0;i<s.size();i++)
            dp.push_back(temp);
    
        vector<int> cut(s.size()+1,0);
        
        for(int i=s.size()-1;i>=0;i--)
        {
            cut[i]=INT_MAX;
        	for(int j=i;j<s.size();j++)
            {
                if(s.at(i)==s.at(j)&&(j-i<=1||dp[i+1][j-1]==1))
                {
                    dp[i][j]=1;
                    cut[i]=min(1+cut[j+1],cut[i]);  
                }
            }
        }
        return cut[0]-1;  
    }
};

在这里插入图片描述

参考:
1.https://blog.csdn.net/yutianzuijin/article/details/16850031
2.https://blog.csdn.net/jin_kwok/article/details/51423222

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值