一、问题描述
设有整数序列b1,b2,b3,…,bm,若存在i1<i2<i3<…<in,且bi1<bi2<bi3<…<bin,则称 b1,b2,b3,…,bm中有长度为n的不下降序列bi1,bi2,bi3,…,bin。求序列b1,b2,b3,…,bm中所有长度(n)最大不下降子序列
输入:整数序列
输出:最大长度n和所有长度为n的序列个数Total
二、分析
此题分为两个部分:求最大不下降序列长度和序列个数。
首先我们考虑求最大长度。设F(i)为前I个数中的最大不下降序列长度。由题意不难得知,要求F(i),需要求得F(1)—F(I-1),然后选择一个最大的F(j) ( j<I, bi>bj ),那么前I个数中最大不下降序列便是前j个数中最大不下降序列后添加bi而得。可见此任务满足最优子结构,可以用动态规划解决。
通过上面的分析可得状态转移方程如下:
F(i)=max{F(j)+1}(j<I , bj<bi)
边界为F(1)=1
显然只需要求序列个数即可。很容易想到下面的方法:
设Total(I)为前I个数中最长不下降序列的个数。
那么,要求Total(i),只需找到所有的Total(j)满足j<I,bj<bi且前j个数最大不下降序列长度为前I个数中最大不下降序列长度+1,并把他们累加起来。即
Total(i)=∑Total(j) (j<I, bj<bi, F(i)=F(j)-1)
在求所有的Total(i)(F(i)=max)的累加和即可。
但这样考虑有一个致命的错误,如
1 2 2
这样一个序列,最大不下降序列长度为2。但如果按上述方法计算序列1 2会算两次。因此,我们对算法进行改进:
对原序列按b从小到大(当bi=bj时按F从大到小)排序,增设Order(i)记录新序列中的I个数在原序列中的位置。可见,当求Total(i)时,当F(j)=F(j+1) , b(j)=b(j+1)且Order(j+1)<Order(i)时,便不累加Total(j)。这样就避免了重复。
综合看来,上述算法的时间复杂度为O(n^2),空间复杂度为O(n),都是可行的。
三、参考程序
{$A+,B-,D+,E+,F-,G-,I+,L+,N-,O-,P-,Q-,R-,S-,T-,V+,X+}
{$M 65520,0,655360}
Program HNOI97_1;
const finp ='input.txt';
fout ='output.txt';
maxN =1000;
var n,len :integer; {len 记录最大长度}
tot :longint; {tot 记录序列个数}
f,b :array[1..maxN]of integer;
Procedure init; {输入}
var f :text;
begin
assign(f,finp);reset(f);
n:=0;
while not eoln(f) do begin
inc(n);
read(f,b[n])
end;
close(f)
end;
Procedure main;
var i,j,t :integer;
order :array[1..maxN]of integer;
total :array[1..maxN]of longint;
begin
f[1]:=1;len:=1; {求解最大不下降序列长度}
for i:=2 to n do begin
f[i]:=1;
for j:=1 to i-1 do
if (b[i]>b[j])and(f[i]<f[j]+1) then
f[i]:=f[j]+1;
if f[i]>len then len:=f[i] {记录最大值}
end;
for i:=1 to n do order[i]:=i;
for i:=1 to n do {排序}
for j:=i+1 to n do
if (b[i]>b[j])or(b[i]=b[j])and(f[i]>f[j]) then begin
t:=b[i];b[i]:=b[j];b[j]:=t;
t:=f[i];f[i]:=f[j];f[j]:=t;
t:=order[i];order[i]:=order[j];order[j]:=t
end;
tot:=0; {计算序列个数}
fillchar(total,sizeof(total),0);
for i:=1 to n do begin
if f[i]=1 then total[i]:=1
else
for j:=i-1 downto 1 do
if (f[j]=f[i]-1)and(b[j]<b[i])and(order[j]<order[i]) then
if (b[j+1]<>b[j])or(f[j+1]<>f[j])or(order[j+1]>=order[i]) then
inc(total[i],total[j]);
if (f[i]=len)and(b[i]<>b[i+1]) then {记录累加最终值}
tot:=tot+total[i]
end;
end;
Procedure out; {输出}
begin
assign(output,fout);rewrite(output);
writeln(len);
writeln(tot);
close(output)
end;
Begin
init;
main;
out
End.