这两周学的DP例题种类很多,这类题最明显的特点就是递归。
最开始碰到的难题实在是想不明白(主要是因为dp[i][j]到底等于什么、i,j的取值范围的思路不清晰)
我们先拿简单的例题来慢慢理清这类问题的思路,然后逐渐提高难度。
例题:
1.这次做题里有个苹果树的题,大概就是说树上的虫子每一单位时间会左右转移(必须),问一树上的虫子最后会去哪。这个题思路算是最简单的了,dp[i][j]=dp[i-1][j-1]+dp[i-1][j+1] (i时间,j树的位置),还有比较重要的部分是初始量的设置。这题的初始量是dp默认全是0;dp[0][p]=1(p是最开始树的位置。时间不能为0)。那么为什么要初始设为1呢?这种量的设置一般会影响最初的几个值。比如我们设时间为1,p=1。eg:dp[1][2]=dp[0][1]+dp[0][3]。dp[1][2]=1+0;此时符合题意。那么之后的值也不会出现问题,这题基本就解决了。
2.还有个比较基础的题是公共子序列。取子列,且不用连续,但需保证子列内元素相对顺序不改变。关键代码是
if(A[i] == B[j])
dp[i][j] = dp[i-1][j-1]+1;
else
dp[i][j] = max(dp[i-1][j] , dp[i][j-1]);
return dp[n][m];
3.还有个求连续子段和。和上面那个题区别在于需要连续子段。关键代码:dp[1]=max(a[i],0)
dp[i]=max(dp[i-1]+a[i],0),和上面对比看就可。
比较简单。
下面找些难题
4.整数划分。这题其实dp的关系还算好找,主要是一些初始量的设置和j取值范围很特殊。核心代码:for(int i = 1;i <= n; i++) {
for(int j = 1;jj <= i2; j++) {//1+2+…+j<=n->(1+j)j<=2i->jj<=2i
dp[i][j] = dp[i-j][j]+dp[i-j][j-1];
分析一下。dp[5][2]=dp[3][2]+dp[3][1];符合题意。那么j的取值怎么来的呢。最小的的子值加起来小于等于n才有可能有机会对,即(1+j)j<=2i。然后再略微扩大下范围,(不影响结果)。
5.尼克的任务。
当i时刻空闲且有任务时,必须选一个任务做,
所以dp[i] 等于dp[i+任务时长]
欲知dp[i+任务时长] 的值->则需要倒序递推从n~1
假设i时刻为空闲状态,则有两种情况
当前时间点无任务开始
dp[i] = dp[i+1] +1;
当前时间点有任务开始
dp[i]=max(dp[i+任务时长],dp[i])这题需要从后往前推,比较特殊。
技巧
1.DP因为要用到相邻变量的值,而我又习惯用i=1开始递归。那么设置数组大小的时候可以给最大值加个5,保证数值不会溢出。
2。要注意初始量的表示,检测办法就是代个开始的数,确定初始值是否合理,然后根据情况修改初始值的默认值。
3.freopen函数可以用来测试的时候直接导入一些比较长的数值,但提交的时候要//掉。
3.多组数据建议DP定义在while里面,防止变量不重置数值。
4.不知道为啥,一些大的二维数组定义全局变量会加快运行速度,要是数值太大建议把局部变为全局,能节省重新写代码的时间。
学习反思
不得不说,DP的难度比贪心高了几个档次,最开始做题完全找不到思路,做这种题还是应该多做,不然光靠思考真的啥也想不明白,做题建议先找几组数据找找规律,dp其实就是递归,你带一带数据其实就会发现思路,它并不那么容易理解,但数据合理后会发现自己猜的的递归方程其实没啥问题。这在前期我的做题效率上有极大的提高。虽然思路难,但递归的方程数量其实并不多,多做题有助于提升直觉的。