题目大意:
一个数的序列bi,当b1 < b2 < ... < bS的时候,我们称这个序列是上升的。对于给定的一个序列(a1, a2, ..., aN),我们可以得到一些上升的子序列(ai1, ai2, ..., aiK),这里1 <= i1 < i2 < ... < iK <= N。比如,对于序列(1, 7, 3, 5, 9, 4, 8),有它的一些上升子序列,如(1, 7), (3, 4, 8)等等。这些子序列中最长的长度是4,比如子序列(1, 3, 5, 8).
你的任务,就是对于给定的序列,求出最长上升子序列的长度。
法一:O(n^2)
思路:转化成子问题:求以ak(k=1,2,3。。。N)为终点(即上升子序列中最右边的数)的最长上升子序列的长度。该问题只和数字的位置有关,因而序列中数的位置k就是“状态”,而状态“k“对应的值就是以ak作为终点的最长上升子序列的长度。假定maxlen(k)表示以ak作为终点的最长上升子序列的长度,则:
maxlen(1)=1;
maxlen(k)=max{maxlen(i):1<i<k且ai<ak且k!=1}+1
这个状态转移方程的思想是:maxlen(k)的值就是在ak左边,终点小于ak,且长度最大的那个上升子序列的长度再加1.因为ak左边任何终点小于ak的子序列加上ak后就能形成一个更长的上升子序列。
以下是代码:
#include <stdio.h>
#define MAX 1000 //千万别加‘;’
int seq[MAX+10];
int seqlen[MAX+10];
int main()
{
int i,j,k,N,max,maxlen=1;
scanf("%d",&N);
seqlen[1]=1; //seqlen数组存以第i个数为终点的最长上升序列
for(i=1;i<=N;i++)
scanf("%d",&seq[i]); //seq数组保存序列数组
for (i=2;i<=N;i++)
{
max=0; //max是用于计算从seq[1]到seq[i-1]的最长上升子序列的值
for (j=1;j<=i-1;j++)
{
if(seq[j]<seq[i]&&seqlen[j]>max) //在前i-1个序列中,寻找以终点小于seq[i]的最长的子序列,即最优子状态
max=seqlen[j];
}
seqlen[i]=max+1;
if(seqlen[i]>maxlen) //seqlen中保存的是第i个数为终点的最长上升序列,找出这个数组中最大的值即为最优序列长度
maxlen=seqlen[i];
}
printf("%d\n",maxlen);
return 0;
}
法二:复杂度O(nlogn)
但法一有个弱点:若MAX的值太大就会超时,因而需要优化。
思路:考虑两个数a[x]和a[y],x<y且a[x]<a[y],且dp[x]=dp[y],当a[t]要选择时,到底取哪一个构成最优的呢?显然选取a[x]更有潜力,因为可能存在a[x]<a[z]<a[y],这样a[t]可以获得更优的值。在这里给我们一个启示,当dp[t]一样时,尽量选择更小的a[x].
按dp[t]=k来分类,只需保留dp[t]=k的所有a[t]中的最小值,设d[k]记录这个值,d[k]=min{a[t],dp[t]=k}。
这时注意到d的两个特点(重要):
1. d[k]在计算过程中单调不升;
2. d数组是有序的,d[1]<d[2]<..d[n]。
利用这两个性质,可以很方便的求解:
1. 设当前已求出的最长上升子序列的长度为len(初始时为1),每次读入一个新元素x:
2. 若x>d[len],则直接加入到d的末尾,且len++;(利用性质2)
否则,在d中二分查找,找到第一个比x小的数d[k],并d[k+1]=x,在这里x<=d[k+1]一定成立(性质1,2)。
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
#define M 41000
int a[M];
int d[M];
int binsearch(int key,int *d,int low,int high)
{
while(low<=high)
{int mid=(low+high)/2; //mid=(low+high)>>1
if(d[mid]<key&&d[mid+1]>=key) //此处的二分法是为了找出d【】数组中从头开始第一个比key小的值,然后插入key
return mid;
else
if(d[mid]>key)
high=mid-1;
else low=mid+1;
}
}
int LIS(int *a,int n,int *d)
{
int i,j,len=1;
d[1]=a[1];
for(i=2;i<=n;i++)
{
if(d[len]<a[i])
j=++len;
else
j=binsearch(a[i],d,1,len)+1; //此处的加1不能忘了。
d[j]=a[i];
}
return len;
}
int main()
{
int m,k,t;
scanf("%d",&t);
while(t--)
{
scanf("%d",&m);
for(k=1;k<=m;k++)
scanf("%d",&a[k]);
printf("%d\n",LIS(a,m,d));
}
return 0;
}
来源:https://www.cnblogs.com/zhtzhl/archive/2012/08/03/2622219.html