最长上升子序列&&最长不下降子序列

百练2757:
题目描述
对于给定的序列,求出最长上升子序列的长度。

题目链接http://bailian.openjudge.cn/practice/2757

解题思路

一、动态规划
1. 找子问题

错误找法:
“求序列的前n个元素的最长上升子序列的长度”是个子问题,但这样分解子问题,不具有“无后效性” 假设F(n) = x,但可能有多个序列满足F(n) = x。有的 序列的最后一个元素比 an+1小,则加上an+1就能形成更长上 升子序列;有的序列最后一个元素不比an+1小……以后的事 情受如何达到状态n的影响,不符合“无后效性”。

正确找法:
“求以ak(k=1, 2, 3…N)为终点的最长上升子序列的 长度” 一个上升子序列中最右边的那个数,称为该子序列的 “终点”。 虽然这个子问题和原问题形式上并不完全一样,但 是只要这N个子问题都解决了,那么这N个子问题的解中, 最大的那个就是整个问题的解。

2. 确定状态

子问题只和一个变量– 数字的位置相关。因此序列中数 的位置k 就是“状态”,而状态 k 对应的“值”,就是以ak 做为“终点”的最长上升子序列的长度。状态一共有N个。

3.找出状态转移方程

maxLen (k)表示以ak做为“终点”的 最长上升子序列的长度
那么有:
maxLen (1) = 1
maxLen (k) = max { maxLen (i):1<=i < k 且 ai < ak且 k≠1 } + 1 若找不到这样的i,则maxLen(k) = 1
maxLen(k)的值,就是在ak左边,“终点”数值小于ak ,且长度 最大的那个上升子序列的长度再加1。因为ak左边任何“终点”小于 ak的子序列,加上ak后就能形成一个更长的上升子序列。

“人人为我”递推型动归程序

 1 #include<iostream>
 2 #include<algorithm>
 3 #include<cstring>
 4 using namespace std;
 5 int main()
 6 {
 7     int a[1010],n;
 8     int maxlen[1010];
 9     cin>>n;
10     for(int i=1;i<=n;i++)
11     {
12         cin>>a[i];
13         maxlen[i]=1;
14     }
15     for(int i=2;i<=n;i++)
16     {
17         for(int j=1;j<i;j++)
18         {
19             if(a[i]>a[j])
20             {
21                 maxlen[i]=max(maxlen[i],maxlen[j]+1);
22 
23             }
24         }
25     }
26     cout<<* max_element(maxlen+1,maxlen+1+n)<<endl;
27     return 0;
28 }

 

 

二、最长上升子序列 (LIS算法(nlong(n)))

设 A[t]表示序列中的第t个数,F[t]表示从1到t这一段中以t结尾的最长上升子序列的长度,初始时设F [t] = 0(t = 1, 2, …, len(A))。则有动态规划方程:F[t] = max{1, F[j] + 1} (j = 1, 2, …, t - 1, 且A[j] < A[t])。

现在,我们仔细考虑计算F[t]时的情况。假设有两个元素A[x]和A[y],满足
(1)x < y < t
(2)A[x] < A[y] < A[t]
(3)F[x] = F[y]

此时,选择F[x]和选择F[y]都可以得到同样的F[t]值,那么,在最长上升子序列的这个位置中,应该选择A[x]还是应该选择A[y]呢?

很明显,选择A[x]比选择A[y]要好。因为由于条件(2),在A[x+1] … A[t-1]这一段中,如果存在A[z],A[x] < A[z] < a[y],则与选择A[y]相比,将会得到更长的上升子序列。
再根据条件(3),我们会得到一个启示:根据F[]的值进行分类。对于F[]的每一个取值k,我们只需要保留满足F[t] = k的所有A[t]中的最小值。设D[k]记录这个值,即D[k] = min{A[t]} (F[t] = k)。

注意到D[]的两个特点:
(1) D[k]的值是在整个计算过程中是单调不下降的。
(2) D[]的值是有序的,即D[1] < D[2] < D[3] < … < D[n]。

利 用D[],我们可以得到另外一种计算最长上升子序列长度的方法。设当前已经求出的最长上升子序列长度为len。先判断A[t]与D[len]。若A [t] > D[len],则将A[t]接在D[len]后将得到一个更长的上升子序列,len = len + 1, D[len] = A [t];否则,在D[1]..D[len]中,找到最大的j,满足D[j] < A[t]。令k = j + 1,则有A [t] <= D[k],将A[t]接在D[j]后将得到一个更长的上升子序列,更新D[k] = A[t]。最后,len即为所要求的最长上 升子序列的长度。

在 上述算法中,若使用朴素的顺序查找在D[1]..D[len]查找,由于共有O(n)个元素需要计算,每次计算时的复杂度是O(n),则整个算法的 时间复杂度为O(n^2),与原来的算法相比没有任何进步。但是由于D[]的特点(2),我们在D[]中查找时,可以使用二分查找高效地完成,则整个算法 的时间复杂度下降为O(nlogn),有了非常显著的提高。需要注意的是,D[]在算法结束后记录的并不是一个符合题意的最长上升子序列!

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #include<iostream>
 5 using namespace std;
 6 
 7 const int mx=100005;
 8 int d[mx];
 9 int main()
10 {
11     int n, T;
12     while (scanf("%d",&n)!=EOF)
13     {
14         memset(d, 0, sizeof(d));
15         int len = 0, x;
16         for (int i=0;i<n;i++)
17         {
18             scanf("%d", &x);
19             if(i == 0)d[++len] = x;
20             else
21             {
22                 if(x > d[len])d[++len] = x;///如果是不下降,这里改为>=
23                 else{
24                     int pos = lower_bound(d + 1, d + len, x) - d;///如果是不下降,这里改为upper_bound(d + 1, d + len, x) - d;
25                     d[pos] = x;
26                 }
27             }
28         }
29         printf("%d\n",len);
30     }
31 }

 

 

posted @ 2018-05-07 21:53  _努力努力再努力x  阅读(400)  评论(0编辑  收藏  举报