最长不降子序列算法详解

最长不降子序列(LIS)算法

自己乱搞了个数据

输入
10
1 3 7 3 6 1 3 2 4 5

输出
6

显然上述数据中最长不降子数列为{1,3,3,3,4,5},长度为六。

一、暴力dp

很容易就能想到的方法:
对于一个序列A[1..n],f[i]表示从Ai到An的最长不降子序列,
则转移方程为:f[i]:=max(f[i],f[j+1])【i:=n downto 1;j:=i+1 to n;f[i]初始为1】
时间复杂度为 O(n^2)
代码:

  var n,i,j,max:longint;
      a,f:array[1..10000]of longint;
  function max1(a,b:longint):longint;
  begin
        if a>b then exit(a)
        else exit(b);
  end;


  begin
        readln(n);
        for i:=1 to n do
                read(a[i]);
        for i:=n downto 1 do
        begin
                f[i]:=1;
                for j:=i+1 to n do
                        if a[j]>=a[i] then
                                f[i]:=max1(f[j]+1,f[i]);
        end;
        for i:=1 to n do
                if f[i]>max then max:=f[i];
        writeln(max);
  end.

显然,在数据>10000时会T。

二、单调队列二分优化

令一个数组B[1..lenb]
b[i]表示从1到i中最长不降子序列中最后一个元素
可以发现B是单调不降的
我们每读入一个Ai,只需维护B单调不降即可
维护用二分查找,查找b[i]满足b[i-1 <= x < b[i+1],复杂度 O(log n)
最后输出lenb即可
总复杂度O(n log n)
代码:

  var n,i,x,l,r,w,mid:longint;
      a:array[0..1000000]of longint;
  begin
        readln(n);
        for i:=1 to n do
        begin
                read(x);
                if x>=a[w] then
                begin
                        inc(w);a[w]:=x;
                end
                else
                begin
                        l:=1;r:=w;
                        while l<r do
                        begin
                                mid:=(l+r) div 2;
                                if a[mid]<=x then l:=mid+1
                                else r:=mid;
                        end;
                        a[r]:=x;
                end;
        end;
        writeln(w);
  end.

谢谢!

欢迎指正错误^_^

点赞