[LIS] 动态规划及其简单优化

{

好久不写文章了

前些日子 在搞联赛 再加上写的都是DP题

自己还没掌握 所以不好写

这次把比较基础的LIS问题写写

}

 

动态规划

Dynamic Programming

简称DP 是解决 决策过程(decision process)最优化  的方法

本文结合一个简单的例子探讨DP的简单优化方法

LIS问题 Longest Increasing Subsequence

这个问题的变种很多 为讨论方便

我们下面的LIS问题 都是指最长不下降子序列的问题

譬如 对于一个序列{1 2 3 3 5 4 6 2}

{1 2 3} {1 5 6} {1 2 2} {1 2 3 3 5 6}

都是它的不下降子序列 其中最后一个是最长的不下降子序列

 

一.经典DP——朴素做法

由于是经典例子 很多人都知道解决这个问题的DP方程

Opt[i]=MAX{Opt[j]}+1 (A[i]>=A[j],1<=j<i)

也很好理解 不再赘述

给出O(N^2)的代码 感觉常数方面做的很好

N最大可以到12000左右

 

LIS-O(N*N)
 
   
// LIS N^ 2 DP
// 16 ~ 05 2010 - 11 - 26
const maxn = 100000 ; { Max_N=12000,Time Limit=1s }
var i,j,n,ans:longint;
opt,a:
array [ 1 ..maxn] of longint;
begin
assign(input,
' lis.in ' ); reset(input);
assign(output,
' lis3.out ' ); rewrite(output);
readln(n);
for i: = 1 to n do
read(a[i]);
readln;
opt[
1 ]: = 1 ;
for i: = 2 to n do
begin
for j: = 1 to i - 1 do
if (a[i] >= a[j]) and (opt[j] > opt[i])
then opt[i]: = opt[j];
inc(opt[i]);
if opt[i] > ans
then ans: = opt[i];
end ;
writeln(ans);
close(input); close(output);
end .

 

 

二.数据结构优化——通用做法

这个朴素的实现 时间复杂度是O(N^2)

我们还可以把它优化到O(NLog2N)

最好想的思路是优化MAX{}的决策

对于要从第一关键字A[]满足小于等于一个数的所有节点中

取得一个第二关键字Opt[]最大的节点

我们考虑运用平衡二叉树优化这种限制范围最大值操作 使单次决策复杂度达到O(Log2N)

这个思路比较通用 很多动态规划都可以用数据结构优化

具体考虑限制范围最大值操作的做法

我们选用严格平衡的平衡树来解决问题以提高效率 我选用了SBT

节点的排序关键字就是A[] 为记录第二关键字 我们给每个节点多记录一个域F[]

记录以当前节点为根的子树的最大第二关键字

SBT在插入和旋转的同时要注意同时更新F[]

可以对平衡二叉树的查询操作Find(x,v)加以修改得到我们需要的操作

先看图

如果最下面的红点是我们普通查询得到的节点的话

这张图描述了从根节点到所查节点的一条路径

不难发现 所有红色节点红色子树的A[]都是小于等于所查节点的A[]值的

而所有蓝色节点蓝色子树都不满足这个限制条件

还可以发现红色部分就是在路径上接下来向右走的节点及其左子树

比较红色节点的Opt[]值和红色子树的F[]值取最大即可

给出平衡树优化过的代码

 

LIS-SBT
 
   
// LIS NLogN DP
// Based On SBT
// 19 ~ 02 2010 - 11 - 26
const maxn = 300000 ; { Max_N=12w,Time Limit=1s }
oo
= maxlongint;
var l,r,f,s,n,a,opt: array [ 0 ..maxn] of longint;
k,i,ans,t,tt:longint;
procedure update(x:longint);
var temp:longint;
begin
f[x]:
= opt[n[x]];
if f[l[x]] > f[r[x]]
then temp: = f[l[x]]
else temp: = f[r[x]];
if temp > f[x] then f[x]: = temp;
end ;
procedure zig( var x:longint);
var y:longint;
begin
y:
= l[x]; l[x]: = r[y]; r[y]: = x;
s[y]:
= s[x]; s[x]: = s[l[x]] + s[r[x]] + 1 ;
f[y]:
= f[x]; update(x);
x:
= y;
end ;
procedure zag( var x:longint);
var y:longint;
begin
y:
= r[x]; r[x]: = l[y]; l[y]: = x;
s[y]:
= s[x]; s[x]: = s[l[x]] + s[r[x]] + 1 ;
f[y]:
= f[x]; update(x);
x:
= y;
end ;
procedure maintain( var x:longint; flag:boolean);
begin
if flag
then if s[l[l[x]]] > s[r[x]] then zig(x)
else if s[r[l[x]]] > s[r[x]]
then begin zag(l[x]); zig(x); end
else exit
else if s[r[r[x]]] > s[l[x]] then zag(x)
else if s[l[r[x]]] > s[l[x]]
then begin zig(r[x]); zag(x); end
else exit;
maintain(l[x],true); maintain(r[x],false);
maintain(x,true); maintain(x,false);
end ;
procedure insert( var x:longint; i:longint);
begin
if x = 0
then begin
inc(tt); x:
= tt;
n[x]:
= i; s[x]: = 1 ;
f[x]:
= opt[i];
end
else begin
inc(s[x]);
if a[i] <= a[n[x]]
then insert(l[x],i)
else insert(r[x],i);
update(x);
maintain(x,a[i]
<= a[n[x]]);
end ;
end ;
function find(x,i:longint):longint;
var temp:longint;
begin
temp:
= 0 ;
while x <> 0 do
if a[i] < a[n[x]]
then x: = l[x]
else begin
if f[l[x]] > temp
then temp: = f[l[x]];
if opt[n[x]] > temp
then temp: = opt[n[x]];
if a[i] = a[n[x]] then break;
x:
= r[x];
end ;
find:
= temp;
end ;
begin
assign(input,
' lis.in ' ); reset(input);
assign(output,
' lis2.out ' ); rewrite(output);
readln(k);
for i: = 1 to k do
read(a[i]);
readln;
opt[
1 ]: = 1 ;
insert(t,
1 );
for i: = 2 to k do
begin
opt[i]:
= find(t,i) + 1 ;
if opt[i] > ans
then ans: = opt[i];
insert(t,i);
end ;
writeln(ans);
close(input); close(output);
end .

 

事实上 运用Splay Tree的Splay操作可以写出更为简洁的代码

不过常数也会随之变大

 

三.单调性优化——最好的做法

上面第二种做法的复杂度已经达到O(NLog2N)

但是常数比较大 只能做到N=12w左右

下面介绍基于单调性的二分查找优化的决策方法

首先对于两个Opt[]相同的决策 我们应当选择A[]较小的 位置更靠前的

于是我们新增数组C[1..ans] 且C[i]记录长度为i的不降子序列A[]值最小为多少 A[]相等取最靠前的

每次只要从C里面查找一个满足条件的节点转移即可

注意到C具有单调性 (证明用反证法+LIS的定义即可)

再加上二分查找即可

给出这种实现的代码 常数比平衡树优化有优势 N=100w没问题

 

LIS-Dichotomy
 
   
// LIS NLogN DP
// Based On Dichotomy
// 16 ~ 35 2010 - 11 - 26
const maxn = 1000000 ; { Max_N=100w,Time Limit 1s }
oo
= maxlongint;
var opt,a,c: array [ 0 ..maxn] of longint;
n,i,k,l,r,ans:longint;
begin
assign(input,
' lis.in ' ); reset(input);
assign(output,
' lis1.out ' ); rewrite(output);
readln(n);
for i: = 1 to n do
read(a[i]);
readln;
ans:
= 1 ;
opt[
1 ]: = 1 ; c[ 1 ]: = 1 ;
for i: = 2 to n do
begin
l:
= 0 ; r: = ans;
while l < r do
begin
k:
= (l + r + 1 )shr 1 ;
if a[c[k]] <= a[i]
then l: = k
else r: = k - 1 ;
end ;
opt[i]:
= l + 1 ;
if (c[opt[i]] = 0 ) or (a[i] < a[c[opt[i]]])
then c[opt[i]]: = i;
if opt[i] > ans
then ans: = opt[i];
end ;
writeln(ans);
close(input); close(output);
end .

 

本文只是对LIS问题的基本优化作一个简单介绍

如有不足 欢迎指正

 

BOB HAN原创 转载请注明出处http://www.cnblogs.com/Booble/

转载于:https://www.cnblogs.com/Booble/archive/2010/11/27/1889482.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值