神奇的动归状态转移方程——最优子序列

最优子序列问题:

给定一个序列,求和最大的连续子序列(因为序列中会有负值存在)。

一些朴素算法就不再说了,还是说说DP。

考虑待查序列a[1...n],定义b[i]为序列a[1...i]最大后缀,于是乎有了如下动归方程:

         b[i]=max{ b[i-1]+a[i] , a[i] 

为什么呢?针对b[i-1]考虑仅有的两种情况:1).若b[i-1]>0,那么a[1...i]的最大后缀必然是a[i]+b[i-1]。2).若b[i-1]<=0,当然a[1...i]的最大后缀就是a[i]了!

现在考虑b[1...n],因为b[i]a[1...i]的最大后缀的和,那么a[1...n]的最大子序列必然是b[i]中的一个(因为最大子序列必然是a[1...n]某个前缀串的后缀串),显然是b[i]中的最大值!

接下来就是一次扫描计算b[1...n],然后找出最大的就OK了!

原理很简单,不过这个DP的动态转移方程……为什么动态规划里的状态转移方程总是那么神奇???!!!

代码如下:

b[0]=a[0];
for(i=1;i<n;i++)
{
    if(b[i-1]>0) b[i]=b[i-1]+a[i];
    else b[i]=a[i];
}
for(i=1;i<n;i++)
    if(b[i]>b[0]) b[0]=b[i];
cout<<b[0]<<endl ;

显然,可以做一个优化!

每一次循环迭代计算b[i],我们只需要知道前一次迭代b[i-1]的值,所以没必要存b[1...n]的所有值。每一次计算b[i],只需要根据前一次的迭代值pre计算本次迭代的值,时刻更新最大值

代码如下:

max=0; pre=0;
for(i=0;i<n;i++)
{
    if(pre>=0) pre+=a[i];
    else pre=a[i];
    if(pre>max) max=pre;
}
cout<<max<<endl;

还有一种迭代方式,这个稍微需要转换一下思维。pre记录当前扫描位置a[i]对应的a[1...n]的前缀a[1...i]最长的非负后缀的和,显然若本次迭代后pre非负,那么就可以作为下一次扫描的前缀!若本次迭代后pre<0,那么pre置零(相当于重新开始记录一个序列)。

因为最终的最大子序列必然没有一个负前缀(否则可以去掉这个前缀以获得更大的子序列),那么a[1...n]的最大子序列必然就会出现在某次pre中(因为a[1...n]的最大子序列必然是迭代过程中的一个非负前缀),每次迭代更新最大值保存最大子序列之和。

代码如下:

max=0;pre=0;
for(i=0;i<n;i++)
{
pre+=a[i];
if(pre>max) max=pre;
if(pre<0) pre=0;
}
cout<<max<<endl;


搞完最大子序列,又想到最大子矩阵,不过最大子矩阵就略微苦逼一点。迭代i , j把第 i~j 压缩至一行,然后利用最大子序列来计算,时间复杂度O(n^3)……

目前貌似没有一个比较好的算法,值得研究一下!

 


转载于:https://my.oschina.net/llmm/blog/122946

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值