4月10日一周学习总结
动态规划
这周是开始学习动态规划的第二周,对动态规划已经有了一个初步的认识,但是在做题方面还是很难独自找到解决办法,以下为这周学习总结。
寻找最长有序子序列问题
这个问题时动态规划问题中的经典问题,但是可以有很多的变式:求最长子序列的和,求两个序列的共同子序列的最长子序列…其基本的解题思路比较统一,一般都是先找动态转移方程。
以下是寻找最大和子序列问题,第一次做这道题的时候认为可以开一个数组来记录当前位置的最大和子序列,然后下一个位置时开始从前面查找找到当前最大子序列相加并继承。
for(int i=1;i<=n;i++)
{
cin>>a[i];
if(a[i]>=0)
flag=1;
int tem=0;
for(int j=i;j>=1;j--)
{
tem+=a[j];
if(tem>maxn)
{
maxn=tem;
be=a[j];
en=a[i];
}
}
}
这样的思路没有问题,但是有运行时间是o(2)级别的,所以提交超时。
int tem=0,p=a[1];
for(int i=1;i<=n;i++)
{
if(tem<0)
{
tem=0;
p=a[i];
}
tem+=a[i];
if(tem>maxn)
{
maxn=tem;
en=a[i];
be=p;
}
}
这是修改后的代码,运行时间变为o(1)级别的,思路上没有本质的变化,设置一个序列,作为上个元素对应的最大和序列,对输入的每个元素进行判断,先定下起始位置,然后根据当前的元素加上上个元素对应的最大值进行判断,若当前最大和序列大于最大值,将当前位置设为结束点,并更新最大值,若小于则继续进行判断,若小于0,则更新起始点和最大和序列为0。
这串代码的好处在于缩短了运算时间,不必给每个元素设置最大和子序列。
以下是有序子序列的变式,给出一组数,有时间要求,奇数时间加,偶数时间减 ,求最后能够得到的最大数,这个题目一开始想的时候题目看了好久,没有找到解题的方向,后来开始对奇偶数进行逐一分析,发现无非是四种情况,分成两个同时进行,一个是奇数步情况,另一个是偶数步情况,当选择步数是奇数时,当前可以继承上一个奇数步,也就是跳过当前元素得到的结果,也可以被偶数步减去,当选择偶数时,当前元素继承上一步的偶数步,也可以让奇数步加上当前元素。
dp1是奇数步,dp2是偶数步
{dp2[0]=dp1[0]=0;
for(int i=1;i<=n;i++)
{
cin>>m;
dp1[i]=maxd(dp1[i-1],dp2[i-1]-m);
dp2[i]=maxd(dp2[i-1],dp1[i-1]+m);
}
这道题目题解看起来很清晰,但是分析这道题的时候思绪很乱,绕了好久才绕过来,对于动态转移方程还是使用不熟练,做题时即使有思路也写不出来。
最后是一道寻找最大和子序列问题的变式,找寻m个子序列的最大和。
for (i = 1; i <= m; i++)///分成几组
{
Max = -inf;
for (j = i; j <= n; j++)///j个数分成i组至少要有i个数
{
dp[j] = max(dp[j-1] + a[j], mmax[j-1] + a[j]);
mmax[j-1] = Max;
Max = max(Max, dp[j]);
}
}
dp[j[为前j个数中选取的最大和,那么我们对于第 j 个数字我们有两种决策,一是加入到最后一段, 二是独立成新段。这道题的复杂度本来是o(m*(n-i)),但一开始想的时候忽略了j个字段至少有j个数这个简单问题。
本周学习感悟和收获
这周最大的收获在于学到了一种实现记忆输出的方法,即将符合题目要求的所有数都输出,但是在dp问题中往往需要对所有情况分析一遍后才能得到最优解的位置。这时候可以设置一个记忆数组,pre[ n ] ,它记录了上个数组符合要求的数组的地址,即n = pre[ n-1 ],这时候既可以输出当前位置n,又可以找到上一个位置n-1,还可以进行倒序输出,这是之前没有接触过的方法,还在学习之中,要增加熟练度。同时,这周还接触了很多新的基础动态规划题目,分析的方法多是对一个元素进行分析,找到其中的动态转移方程,个人感觉类似于递归,而且个人感觉这类算法题目比之前接触的题目都要难把握解题思路,往往一道题要想半小时也不得要领,看题解时更是要看上好久,对于现在的题目还是没有完全吃透,仍要重复练习。