对于最长不下降子序列一题,问题描述如下:
有长度为N的序列:
A1 A2 …..An
求最长不下降子序列:Ai1,Ai2,,,,,Aik, 其中ai1<=ai2<=.....<=aik
求最长不下降子序列的长度
一看最长不下降子序列,最先想到的肯定是O(n²)的DP解法,当然,这里还是写下代码,加深理解:
var
a,f:array[1..1000] of longint;
n,i,j,ans:longint;
function max(x,y:longint):longint;
begin
if x>y then exit(x) else exit(y);
end;
begin
readln(n);
for i:=1 to n do
read(a[i]);
for i:=n-1 downto 1 do
for j:=i+1 to n do
if a[i]<=a[j] then
begin
f[i]:=max(f[i],f[j]+1);
ans:=max(ans,f[i]);
end;
writeln(ans+1);
end.
(以下简称最长不下降子序列为LS)
但是,我们知道,其实这种方法是很笨的,因为有很多数明显不能构成一个最优的序列,我们还拿它去比较,就是很多余的。
但这不能构成一个最优序列是什么意思呢?
假设当前构成的LS的长度为3,最后一位的数为7,那如果现在又加进一个数,也可以与前面的数构成长度为3的LS,那么,到底是以6结尾好?还是7结尾好呢?毋庸置疑。。
当然是6.
当然是最后一位越小越好啊,这样后面构成LS的长度才有可能更优,根据这一思路,我们就可以大概知道我们到底怎么去优化LS了,那到底怎们实现呢?
所以我们可以多建一个f数组表示以长度为i的最长不下降子序列的最后一个数是多少。每次增加一个数x,我们都需要去更新f数组的值,找到一个刚好的下标i使得f[i]>x,则可把f[i+1]赋值为x.
很明显这种方法是易证的.
代码:
var
a,f:array[1..100000] of longint;
n,i,k,len:longint;
function find(len,n:longint):Longint;
var
l,r,mid:longint;
begin
l:=1; r:=len;
while l<=r do
begin
mid:=(l+r) div 2;
if f[mid]=n then exit(mid);
if f[mid]>n then r:=mid-1 else l:=mid+1;
end;
exit(l);
end;
begin
readln(n);
for i:=1 to n do
read(a[i]);
len:=1;
f[1]:=a[1];
for i:=2 to n do
begin
k:=find(len,a[i]);
f[k]:=a[i];
if k>len then len:=k;
end;
writeln(len);
end.
但是,对上面程序做一番思考之后,会发现,此方法只是使用于最长递增子序列,即所输入的数不能有相等的数,但我们要探讨的是LS,所以,我们可以只需对上述程序做一个当相等的时候暴力求位置即可,但这种方法当最坏情况的时候效率还是有可能接近O(n²)的.
当然,我们也可以对二分改动一下,把L,r更新时有些数会重复的这些情况做相应的改变也可以 .最坏复杂度也是O(n²)