首先从斐波那契数列来看动态规划,递推公式为:f(n+2)=f(n+1)+f(n)。
若普通求解f(n),则需要一直往下递推,直到f(1)和f(2)为止,但用动态规划,使用dp数组储存每个阶段的f(n),则每次f的求解都不需要循环往下计算,只需调用dp数组中n-1与n-2阶段的f值即可。
这样大大节约运行时间,动态规划也就是做这么件事。
动态规划的使用场景:各阶段的最优解有一定的相关型,某一阶段的最优解可以根据前一阶段的最优解再计算的来。
dp数组是最优解数组,任意数据是当前情况下的最优解(动态规划的核心)
如何确定dp数组(如何计算出dp数组)是解题的关键。(找出规律)
动态规划的题型我大概将其分为两类:
一:求最优解问题。(背包问题)
二:求解的个数问题。(整数的k拆分问题)
最优解问题:
最优解问题最有代表性的是背包问题。
问题描述:
有n个重量分别为{w1,w2,…,wn}的物品,它们的价值分别为{v1,v2,…,vn},给定一个容量为W的背包。
设计从这些物品中选取一部分物品放入该背包的方案,每个物品要么选中要么不选中,要求选中的物品不仅能够放到背包中,而且重量和为W具有最大的价值。
问题解析:
对于可行的背包装载方案,背包中物品的总重量不能超过背包的容量。 最优方案是指所装入的物品价值最高,即 v1x1+v2x2+…+vn*xn(其中xi取0或1,取1表示选取物品i)取得最大值。
问题求解:
设置二维动态规划数组dp,dp[i][r]表示背包剩余容量为r(1≤r≤W),已考虑物品1、2、…、i(1≤i≤n)时背包装入物品的最优价值。显然对应的状态转移方程如下:
dp[i][0]=0(背包不能装入任何物品,总价值为0) 边界条件dp[i][0]=0(1≤i≤n)―边界条件
dp[0][r]=0(没有任何物品可装入,总价值为0) 边界条件dp[0][r]=0(1≤r≤W)―边界条件
dp[i][r]=dp[i-1][r] 当r<w[i]时,物品i放不下
dp[i][r]= MAX{dp[i-1][r],dp[i-1][r-w[i]]+v[i]} 否则在不放入和放入物品i之间选最优解
由此也易得dp[n]W]为所求。
附上代码:
void Knap()
{ int i,r;
for (i=0;i<=n;i++)
dp[i][0]=0;
for (r=0;r<=W;r++)
dp[0][r]=0;
for (i=1;i<=n;i++)
{ for (r=1;r<=W;r++)
if (r<w[i])
dp[i][r]=dp[i-1][r];
else
dp[i][r]=max(dp[i-1][r],dp[i-1][r-w[i]]+v[i]);
}
}
解的个数问题:
典型问题:整数的k项拆分。
问题描述:
一个正整数x拆成k个正整数,k个正整数的和为x,k个正整数可以重复。
(5=1+1+3与5=1+3+1为同解)
问题解析:
问题很简单,即找出x1,x2,x3,…,xk的和等于x,求有多少组这样的x1,x2,x3,…,xk满足条件。
问题求解:
设置二维动态规划数组dp,dp[i][r]表示i的r拆分,显然对应的状态转移方程如下:
dp[i][0]=0(一个整数的0拆分等于0) 边界条件dp[i][0]=0(1≤i≤n)―边界条件
dp[0][r]=0(0的r拆分为0) 边界条件dp[0][r]=0(1≤r≤W)―边界条件
dp[i][r]=0 当i<k时。 i不可能拆出k项来。
dp[i][r]=dp[i][r-1]+1 当i=k时。(多出来的那一种组合全为1)
dp[i][r]=dp[i-1][r-1]+dp[i-r][r] 当i>k时。(即含1的情况和不含1的情况的和)
所以dp[x][k]为所求。
附上代码:
void DP()
{
int i=0,j=0;
for(i=1;i<=n;i++)
{
for(j=1;j<=k;j++)
{
if(j==1)
{
dp[i][j]=1;
continue;
}
if(i==j)
{
dp[i][j]=1;
continue;
}
if(i<j)
dp[i][j]=0;
else
dp[i][j]=dp[i-1][j-1]+dp[i-j][j];
}
}
}
从上面的总结可以看出,动态规划求解问题的关键在于状态转移方程的归纳,也就是dp数组的求解,方法很多,我觉得逐步分析是最可行的,有从前向后推导也可以从后向前推导。
在这里另外介绍一下滚动数组:(关系到dp数组空间的浪费)
从上面的问题可以看出,每次求解过程中只涉及到了i,i-1两个层次,求出来的解算一个层次,一共就三个层次,而我们却用了大量的空间储存了多余的解,由此引入滚动数组。
以斐波那契数列求解为例:
int Fib2(int n)
{ int dp[3];
dp[1]=1; dp[2]=1;
for (int i=3;i<=n;i++)
dp[i % 3]=dp[(i-2)%3]+dp[(i-1)%3];
return dp[n%3];
}
很多二维数组在求解过程中都可以用滚动数组来代替(是个强有力的办法)。
dp很强也很难,多加练习!!!