动态规划
动态规划(Dynamic programming)是一种在数学、计算机科学和经济学中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。 动态规划常常适用于有重叠子问题和最优子结构性质的问题,动态规划方法所耗时间往往远少于朴素解法。 动态规划背后的基本思想非常简单。大致上,若要解一个给定问题,我们需要解其不同部分(即子问题),再合并子问题的解以得出原问题的解。 通常许多子问题非常相似,为此动态规划法试图仅仅解决每个子问题一次,从而减少计算量: 一旦某个给定子问题的解已经算出,则将其记忆化存储,以便下次需要同一个子问题解之时直接查表。
主要有下面几个DP类型:线性DP(股票系列、最长子序列和(见简单DP))、区间DP(最长回文子串)、背包DP、状态压缩DP、递归DP(爬楼梯、斐波那契数)、树状DP等
这里只写了线性DP、区间DP、背包DP这三个dp,剩下几个万能的百度也能找到
线性DP
虽然说是线性,但是实际上看起来也没有这么线性,主要是如何定义状态方程之间的联系,找到状态转移方程,找到状态转移方程之后,写出完整的代码就简单了。状态转移之间一般是利用上一个或者给定的区分值进行遍历,对于循环的正序或者倒序,主要看定义的状态转移方程是否还利用上一状态的值
最长子序列和问题
dp[i]表示以第i个数结尾的最长子序列和
转移方程:
//要么相连,要么自己一个数组
dp[i]=max{dp[i-1]+num[i],dp[i]}
最长子序列问题
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010;
int dp[N];
int f[N];
int max1;
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++) cin>>f[i];
for(int i=1;i<=n;i++)
{
dp[i]=1;
//以i结尾的最大序列长度
for(int j=0;j<=i;j++)
{
if(f[i]>f[j]) dp[i]=max(dp[j]+1,dp[i]);
}
max1=max(max1,dp[i]);
}
cout<<max1<<endl;
return 0;
}
背包DP
注意里面的改变是减去重量而不是减去1或者加上1
01背包问题
f[i][j]//j代表容量,i代表第几个物品
//f[i][j]代表再第i个物品截止,在能承载重量为j时的最大值
/*转移方程:
第一种:不放入背包,则f[i][j]=f[i-1][j]
第二章:放入背包,如果放的下:
f[i][j]=f[i-1][j-w[i]]+v[i]
如果放不下:f[i][j]=f[i-1][j]
两者进行比较,看看时放入大还是不放入大
*/
//为什么是从j=v开始,j--(只有一维数组才是)
//因为如果是正序,当更新到f[3]时,f[2]已经更新了,
//不再是我们需要的f[2]
改编之AcWing 1047. 糖果(深入理解不合法状态) - AcWing](https://www.acwing.com/solution/content/108139/)
问题:
背包问题是不超过;糖果问题是恰好;
在背包问题中,f[0] [j]都是由意义的
在糖果问题中,只有f[0] [j%k]才是有意义的
所以在糖果问题中,下标是要取mod
dp[i][j]//代表前i个物品的总价值%k=j的集合在这里限制是关于%k余数是多少
/*转移方程
dp(i,j)=max(dp(i−1,j),
dp(i−1,(((j−w[i])%k)+k)%k) +w[i])
不选的话:就是前面那个的值
选的话,就是
子集合都包括第i个物品,并且前面选择的商品和S满足 (S+a[i])%k=j
而我们需要从和这个S有关的集合,状态记录的都是%k
后的结果,S为前i-1个商品的总和,j为前i个商品的余数
所以S%k=(j−a[i])%k)为上一个状态的值
*/
3.初始化:
dp(0,0)=0,dp(0,i)(i≠0)
都是不合法的状态,所以必须要初始化为负无穷,不然的话可以选择先初始化第一件物品的所有情况,再迭代第二个物品之后 (仔细想想,如果不合法的状态初始化为0了,后面递推的时候,答案就会变大)
4.注意事项:
转移方程中的(j−w[i])%k)可能为负的,必须要将余数变成[0,n−1]之间,所以cpp负数取余要变成(j−w[i])%k+k)%k
5、正逆序问题
由于它是二维数组,正逆序更新不会影响当前,所以可以正序可以逆序
区间DP
此题是回文串问题,
回文串问题的思路,此问题的目的是两边一起走
如果左右端的值相等那么只需要判断i+1,j-1的是否相等。拆分成了一个小区间。
此题可以运用dfs进行运算,如果左边和右边相等,那么判断i+1,j-1,如果不相等,判断i+1,j与i,j-1;那一边删去更小
退出条件:1、左边大于右边返回0
2、f[l][r]中已经判断了最小值是多少,可以直接返回
动态规划求解思路:
dp[i][j]:代表区间[i,j]里去掉字符变成字串str(i,j)的集合
状态转移:
1、如果两边相等,dp[i][j]=dp[i+1][j-1]
2、如果两边不相等,dp[i][j]=
min(dp[i+1][j],dp[i][j-1])+1;
枚举顺序:因为要用到i+1的状态所以i要逆序枚举
注意:
当i==j−1且s[i]==s[j]时,这也是回文串,但是dp(i+1,j−1)是不合法的,被声明称了INF.当时,这也是回文串但是不合法的,被声明称了,所以要单独拿出来讨论。
注意其枚举方法,i与j