低价购买(动规例题)

题目描述:

求一个最长不下降子序列,以及这个最长不下降子序列在这个序列里的个数(不能重复)。

对于第一问我们就只要普通的dp一下就行了,(以下将最长不下降子序列称为最长序列)对于第二问这里要详细的讲一下。
状态有如下:
f[i]表示到第i个数的最长序列;
b[i]表示到第i个数的最长序列的个数;
很容易得出方程:
f[i]:=max{f[j]+1}; b[i]:=max{b[j]+1}(1<=j<=i-1,且f[j]+1=f[i]);
但是b[i]的状态仅仅这样就可以了吗?

对于如下的一个序列:
5
69 68 64 67 62
对于求最长序列我们很简单的知道是4.那么有多少个最长序列呢?
69 68 64 62
69 68 67 62
显然有两个。

那这两个是如何得出的呢?
我们可以先注意到前4个数在b数组里都是1,只有第5个是2,这是为什么呢?
先看一下数组:
f=1 2 3 3 4
b=1 1 1 1 2
其实f[5]是由f[4]得来的(j是逆推的,从4~1),然后进行到j=3的时候,f[3]+1=f[5],也就是说f[5]也可以由f[3]得来,所以这里就需要把b[i]加上b[j]了(简称为work)。
然后当把f[i]更新的时候,一定要把b[i]更新,因为你现在是以一个新的序列结尾了,之前的一律不算了。

再举一个栗子:
5
69 68 67 67 62
这里如果按照上面的算法,则最长序列的个数为2.实际上因为题目说了不能重复最长序列,则指在i=5的时候,j=3不能加上b[j]。那这如何解决呢?——可以把当前f[i]是由哪一个得来的标记一下,例如i=5是由j=4得来的,则a[4]——第4个数标为false,如果再遇到与a[4]相同的如a[3]就不能work了。
所以b[i]的正确状态是:
b[i]:=max{b[j]+1}(1<=j<=i-1,且f[j]+1=f[i],bz[a[j]]);

结束后就是输出了,如何输出呢?这里有一个巧妙的处理,就是把i的取值扩到1,变为n+1,而a[n+1]=0,然后输出f[n+1]-1,b[n+1],这有什么好处呢?
其一:输出f[n+1]-1这个很容易理解,这就不用再O(n)找一便最大的了,f[n+1]一定就是最大的+1,减1就是最长序列了。
其二:这里输出b[n+1]有什么用呢?——我们再看一个栗子:
8
1 2 3 4 5 6 7 8
f,b数组的状态如下:
f=1 1 1 1 1 1 1 1
b=1 1 1 1 1 1 1 1
输出:1 8
b数组的前8个都是1,可实际上最长序列的个数却是8,但因为始终没有满足b数组work的条件,所以如果这里再算一个n+1的话,则f[9]=2,然后在j=8的时候就会让b数组work了。最后就会剩下7个累加进去,1+7=8,则最后的b[n+1]就等于8了。

这样有什么好处?显而易见了,这样就可以把每一个可能相等的最长序列做一个最终的合并,可以得到正确的答案了、

var
        i,j,k,n:longint;
        a,b,f:array[0..5000] of qword;
        bz:array[0..50000] of boolean;
begin
        readln(n);
        for i:=1 to n do
                read(a[i]);					//输入
        f[1]:=1;
        b[1]:=1;						//初始值设定
        for i:=2 to n+1 do
        begin
                f[i]:=1;
                b[i]:=1;					//初值
                for j:=i-1 downto 1 do
                        if (a[i]<a[j]) then
                                if f[j]+1>f[i] then		//判断当前是否可以组成“更”长的序列
                                begin
                                        f[i]:=f[j]+1;
                                        fillchar(bz,sizeof(bz),true);	//这里更新bz数组因为新的一个最长序列必须更新以保证bz数组的不变,因为bz=true表示的是以相等的数时,这个位置更优,而当新的序列更新的时候前面的所有更优位置都需要重新更新。
                                        bz[a[j]]:=false;
                                        b[i]:=b[j];
                                end
                                else
                                if (f[j]+1=f[i]) and bz[a[j]] then    //当这两个条件满足时,则这个方案既没重复,又需累加。
                                begin
                                        bz[a[j]]:=false;		//这也是非常重要的更新,加上这个才能避免重复计算b[j]
                                        b[i]:=b[i]+b[j];
                                end;
        end;
        writeln(f[n+1]-1,' ',b[n+1]);
end.

总结:这一题目比较难,而且方法不唯一,需要好好消化。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值