题目描述:
求一个最长不下降子序列,以及这个最长不下降子序列在这个序列里的个数(不能重复)。
对于第一问我们就只要普通的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.
总结:这一题目比较难,而且方法不唯一,需要好好消化。