最长上升子序列(lis.pas/c/cpp)
【题目描述】
给出一个长度为N的整数序列,求出包含它的第K个元素的最长上升子序列。
【输入格式】
第一行两个整数N,K
第二行N个整数
【输出格式】
一个整数L如题目所说的序列长度。
【输入样例】
8 6
65 158 170 299 300 155 207 389
【输出样例】
4
【数据范围】
0<N<=200 000,0<K<=N
【hint】
写出正确部分数据程序的同学会得到部分分。
首先,以 k 为分界点,在前后两段分别求一次最长不上升子序列(nlogn),要求,在他前面的子序列的最后一项要比他小,在他后面的子序列的第一项要比他大。(因为要把这个 a[k] 接在中间)。
代码(SueMiller)
var
n,i,l,r,x,mid,kk,ans,temp:longint;
f:array[0..200010]of longint;
a:array[0..200010]of longint;
begin
assign(input,'lis.in');reset(input);
assign(output,'lis.out');rewrite(output);
read(n,kk);
for i:=1 to n do
read(a[i]);
for i:=1 to kk-1 do
begin
if a[i]>=a[kk] then continue;
x:=a[i];
l:=1;r:=f[0];
while l<r do
begin
mid:=(l+r)shr 1;
if f[mid]<x then
l:=mid+1 else r:=mid;
end;
if (l>r)or((l=r)and(x>f[f[0]]))then
begin
f[0]:=f[0]+1;
f[f[0]]:=x;
l:=f[0];
end else
if x<f[l] then
f[l]:=x;
end;
ans:=f[0];
fillchar(f,sizeof(f),0);
for i:=kk+1 to n do
begin
if a[i]<=a[kk] then continue;
x:=a[i];
l:=1;r:=f[0];
while l<r do
begin
mid:=(l+r)shr 1;
if f[mid]<x then
l:=mid+1 else r:=mid;
end;
if (l>r)or((l=r)and(x>f[f[0]]))then
begin
f[0]:=f[0]+1;
f[f[0]]:=x;
l:=f[0];
end else
if x<f[l] then
f[l]:=x;
end;
ans:=ans+f[0]+1;
writeln(ans);
close(input);close(output);
end.
PS : 最长 XX 子序列 nlogn 【转】
最长上升、下降子序列
var
n,i,l,r,x,mid:longint;
f:array[0..10000]of longint;
begin
read(n);
for i:=1 to n do
begin
read(x);
l:=1;r:=f[0];
while l<r do
begin
mid:=(l+r)shr 1;
if f[mid]<x then //若求最长下降子序列则是f[mid]>x
l:=mid+1 else r:=mid;
end;
if (l>r)or((l=r)and(x>f[f[0]]))then //若求最长下降子序列则是x<f[f[0]]
begin
f[0]:=f[0]+1;
f[f[0]]:=x;
l:=f[0];
end else
if x<f[l] then //若求最长下降子序列则是x>f[l]
f[l]:=x;
end;
write(f[0]);
end.
引用Matrix67的话(求最长上升子序列):
f表示长度为i的上升子序列最后一个数最小是多少。显然数组f是单增的。
读到一个新的数x后,找到某个i使得x>f[i]且x<=f[i+1],于是用x去更新f[i+1];特别地,如果所有的f[i]都小于x,则增加f的长度。
最后看f数组有多长就行了。
由于f单增,所以查找i时可以用二分查找,因此时间复杂度为O(nlogn)。
举个例子,假如序列为 3 2 8 6 7 4 5 7 3,则f数组的变化过程如下:
3
2
2 8
2 6
2 6 7
2 4 7
2 4 5
2 4 5 7
2 3 5 7
最后,f的长度达到4,因此答案为4。
注意,最后的f数组不一定是最长上升子序列的一个方案。
最长不上升、下降子序列
题目:求 1 3 5 2 4 10 4 8 的最大不下降子序列
首先我们定义一个数组RES,则RES[i] 表示当子序列长度为I时子序列最后一位的最小值。
我们用LENGTH来记录这个数组的有效长度。
处理方法:当我们新读入一个数W,我们比较其与RES[LENGTH]的大小
1)若W>=RES[LENGTH]; 那么 LENGTH:=LENGTH+1; RES[LENGTH]:=W; 即在已求出的最长序列RES[LENGTH]后在接上W,则长度加1。
2)若W<RES[LENGTH] 那么从1..RES[LENGTH] 里面找到一个最大的数RES[J](特别注意,若有相同的数如:要插入5 且RES=[1,2,4,4,4,6] 则取最后一个4,这一点表现在后
面程序中mid>x 而不能去等上),使其满足RES[J]<W,
并且我们更新RES[J+1]:=W;
即找到第一个(从左往右数)比带插入的大的数,然后更新为待插入的数。
最后当我们处理完原序列所有数字后,LENGTH 即为最大不下降子序列的长度。
当我们完成2)这个步骤的时候我们采用2分查找的方法,则可将时间复杂度从n降为Logn;
需要注意的是,数组RES在算法结束后记录的并不是一个符合题意的最长上升子序列!
程序实现如下:
(以下程序已在FREE PASCAL 中编译并运行成功)
program LIS;
const
inf='LIS.in';
outf='LIS.out';
maxn=100;
var
x:array[1..maxn] of integer;
res:array[0..maxn] of integer;
length,n,temp:integer;
procedure init;
var i:integer;
begin
readln(n);
for i:=1 to n do
read(x[i]);
end;
procedure find(v1,v2,x:integer); {二分查找,即找到第一个比待插入数大的数}
var mid:integer;
begin
if v1=v2 then begin temp:=v1; exit; end;
mid:=res[(v1+v2) div 2];
if mid>x then //注意不能取等号,若取了等号就会出现把相同的数覆盖掉的问题
find(v1,(v1+v2) div 2,x)
else
find(((v1+v2) div 2)+1,v2,x);
end;
procedure main;{LIS}
var i:integer;
begin
length:=0;
for i:=1 to n do
begin
if x[i]>=res[length] then
begin
inc(length);
res[length]:=x[i];
end
else begin
temp:=0;
find(1,length,x[i]);
res[temp]:=x[i]
end;
end;
end;
procedure outit;
begin
writeln(length);
end;
begin
assign(input,inf);assign(output,outf);
reset(input);rewrite(output);
init;
main;
outit;
close(input);close(output);
end.
得出答案为5。