最长不下降序列(HNOI’97)

一、问题描述

设有整数序列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<Ibj<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.

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值