【题目描述】
设有由n(1≤n≤200)n(1≤n≤200)个不相同的整数组成的数列,记为:b(1)、b(2)、……、b(n)b(1)、b(2)、……、b(n)若存在i1<i2<i3<…<iei1<i2<i3<…<ie 且有b(i1)≤b(i2)≤…≤b(ie)b(i1)≤b(i2)≤…≤b(ie)则称为长度为e的不下降序列。程序要求,当原数列出之后,求出最长的不下降序列。
例如13,7,9,16,38,24,37,18,44,19,21,22,63,15。例中13,16,18,19,21,22,63就是一个长度为7的不下降序列,同时也有7 ,9,16,18,19,21,22,63组成的长度为8的不下降序列。
【输入】
第一行为n,第二行为用空格隔开的n个整数。
【输出】
第一行为输出最大个数max(形式见样例);
第二行为max个整数形成的不下降序列,答案可能不唯一,输出一种就可以了,本题进行特殊评测。
【输入样例】
14 13 7 9 16 38 24 37 18 44 19 21 22 63 15
【输出样例】
max=8 7 9 16 18 19 21 22 63
再加上一问,求当有多少种方案得到最长不下降子序列
思路:单纯的求最长不下降子序列的长度再熟悉不过,不必多说。关键是那两问如何解决?
那就是另外开两个数组。首先,输出一种方案,开一个pre数组,用来记录前面的轨迹。比如说a[j]+1>l,那么更新,pre[i]=j(a[i]前面的数是a[j]),最后找到其中一个最大值,输出即可,详情见代码
其次,求方案数,开一个g数组。如果l>f[i],那么说明到a[i]是要经过a[j]的,所以g[i]=g[j],同时更新,f[i]=l。如果l==f[i](也有可能是由l>f[i]得来的,也就是说如果l>a[i],要跑两个if),根据加法原理,两个都行,那么就相加,也就是g[i]+=g[j]
由以上思路,简单写一下核心代码:
for(int i=1;i<=n;i++)
{
f[i]=1;g[i]=1;pre[i]=0;//因为一个数本身算一个,所以f数组当然初始化为1.而选这个数肯定也至少有一种方案,因为无论如何都可以选。把pre数组初始化是为了在输出的时候用while
for(int j=1;j<i;j++)
{
if(a[j]<=a[i])
{
int l=f[j]+1;
if(l>a[i])
{
g[i]=0;//这里跟思路不太一样,让g[i]=0,因为当它等于0时,下一个if相加就相当于g[i]=g[j],如果将两个if倒过来,应该可以写成g[i]=g[j]
a[i]=l;
pre[i]=j;
}
if(l==a[i])
{
g[i]+=g[j];
}
}
最后的话只要循环一遍f数组,找到最大的其中那个值,另开一个数组,因为pre数组是倒着的,比如说最大的那个值是第i位,那么用一个while,就是当pre数组不等于0的时候,使答案数组等于pre数组,pre数组=pre[pre]......然后答案数组编号++就可以了
优化:
假设a[i]>a[j],且f[i]<=f[j],那么a[i]就已经没有用了,比如说1,4,3,5,易得它们的最长上升子序列长度分别为1,2,2,3,其中4和3就是上升子序列长度一样,而且前面的数小于后面的数,这样前面的数就没有用了,可以删掉。这是什么原理呢,其实整体感知一下就可以:
如果一个子序列的末尾数越小,那么可以接在它后面的上升的数就越多,也就是越有可能,如果能接在小的数后面,那也肯定能接在大的数后面,并且接在小数后面更优,所以就可以更新掉,核心代码如下:
int z[233],f[233],a[233];//a数组和f数组还是原本的含义,新开的z数组的意思是编号,是位置,z[i]=j的含义就是以a[j]为结尾的最长不下降子序列长度为i
int cnt=0//定义一个变量,含义就是当前最长不下降子序列的长度
for(int i=1;i<=n;i++)
{
f[i]=1;//初始化
(以下两行循环可以用二分优化,但是我不会......)
for(int j=1;j<=cnt;j++)
if(a[z[j]]<=a[i]) f[i]=max(f[i],j+1);//如果存储在以这个数为末尾不下降子序列长度为j的数小于等于当前的数,那么这个数为末尾的不下降子序列就应该是本身和j+1的最大值
if(f[i]>cnt) cnt++,z[cnt]=i;//如果当前的不下降子序列长度已经超过了最大值,那么更新最大值,同时将末尾数存入数组
else//如果没有超过
{
if(a[i]<a[z[f[i]]]) z[f[i]]=i;//如果当前的数比存储在长度为f[i]的数小的话,更新
}
}