1.01背包问题
0-1 背包问题:给定 n 种物品和一个容量为 V的背包,物品 i 的重量是 wi,其价值为 vi 。
问:应该如何选择装入背包的物品,使得装入背包中的物品的总价值最大?
首先回顾动态规划的内容,DP与分治区别在于划分的子问题是有重叠的,解过程中对于重叠的部分只要求解一次,记录下结果,其他子问题直接使用即可,减少了重复计算过程。
- 最优子结构性质:最优解包含了其子问题的最优解,不是合并所有子问题的解,而是找最优的一条解线路,选择部分子最优解来达到最终的最优解。
- 子问题重叠性质:先计算子问题的解,再由子问题的解去构造问题的解(由于子问题存在重叠,把子问题解记录下来为下一步使用,这样就直接可以从备忘录中读取)。其中备忘录中先记录初始状态。
(1)求解思路
- 将原问题分解为子问题(子问题和原问题形式相同,且子问题解求出就会被存);
- 确定状态:01背包中一个状态就是N个物体中第i个是否放入体积为V背包中;
- 确定一些初始状态(边界状态)的值;
- 确定状态转移方程,如何从一个或多个已知状态求出另一个未知状态的值。(递推型)
(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