概念
一个经典问题。
有长度为N的序列:
A1 A2 …..An
求最长不下降子序列:Ai1,Ai2,,,,,Aik, 其中ai1<=ai2<=…..<=aik
求最长不下降子序列的长度
例如:(1 2 3 3 4 5) 为一个不下降子序列。
O(N^2)
最容易想到的方法,设F[i]表示以第i位为结尾时最长不下降子序列的长度。
F[i]=max(F[j])+1 (a[i]>=a[j])
代码
var
a:array[1..100] of longint;
f:array[1..100] of longint;
n,i,j,ans:longint;
begin
readln(n);
for i:=1 to n do
begin
read(a[i]);
f[i]:=1;//初始化,每个位置的初始长度都为1
end;
ans:=1;
for i:=2 to n do
begin
for j:=1 to i-1 do
if (a[i]>=a[j]) and (f[i]<=f[j]) then//如果符合要求且长度更长(等同于f[i]<f[j]+1,避免重复拖慢时间)
f[i]:=f[j]+1;
if f[i]>ans then//更新答案
ans:=f[i];
end;
writeln(ans);
end.
Temp数组优化
O(N^2)在数据较大时会超时。
设Temp[i]表示长度为i最长不下降子序列末尾的最小值,ans表示其长度。
每加入一个新数a[i]时,根据数的大小来进行处理。
①a[i]大于等于Temp[ans]
可以直接把a[i]放在第ans+1位。
②a[i]小于Temp[ans]
a[i]不能直接放在列首,但是如果直接丢掉未免太浪费。
试想一下,加入有一列数要加入最长不下降子序列,那么是序列里的数小好加还是序列里的数大好加?
为了让最终结果尽量大,序列中的数就要尽量小。
因为不能放在列首,所以只能用a[i]替换掉Temp中的一个数。
(1).这个数一定要比a[i]大。(否则就没有意义了)
(2).这个数一定是第一个比a[i]大的数。(否则就不是最长不下降子序列)
满足这两个要求,就可以通过维护Temp数组来计算最终答案。
代码
var
a:array[1..100] of longint;
temp:array[0..100] of longint;
n,i,j,ans:longint;
begin
readln(n);
for i:=1 to n do
read(a[i]);
ans:=1;
temp[0]:=maxlongint;
temp[1]:=a[1]; //初始化
for i:=2 to n do
begin
if a[i]>=temp[ans] then //如果大于最大数
begin
inc(ans); //放在第ans+1位
temp[ans]:=a[i];
end
else
begin
for j:=1 to ans do //从小到大找第一个大于a[i]的Temp[j]
if a[i]<temp[j] then
begin
temp[j]:=a[i]; //替换
break;
end;
end;
end;
writeln(ans);
end.
O(NlogN)
其实很简单,只要把上面的循环j改成二分查找就行了。
代码
var
a:array[1..100] of longint;
temp:array[0..100] of longint;
n,i,j,ans,l,r,mid:longint;
begin
readln(n);
for i:=1 to n do
read(a[i]);
ans:=1;
temp[0]:=maxlongint;
temp[1]:=a[1];
for i:=2 to n do
begin
if a[i]>=temp[ans] then
begin
inc(ans);
temp[ans]:=a[i];
end
else
begin
l:=1;
r:=ans;
mid:=(l+r) div 2;
while l<r do //二分查找j
begin
if a[i]>=temp[mid] then l:=mid+1
else
r:=mid;
mid:=(l+r) div 2;
end;
temp[mid]:=a[i];
end;
end;
writeln(ans);
end.
参考:http://blog.csdn.net/fengyingjie2/article/details/54971432