题目链接:https://www.vijos.org/p/1369
http://wikioi.com/problem/2188/
分析:
n^2的算法都会做,但是超时,因此我们要寻找nlog(n)的算法。
虽然n^2的算法会超时,但是我们还是从这里开始分析。
n^2算法:
两个数组,a[i]存储每个元素,f[i]存储以a[i]结尾的子序列
两重循环,第一重(i)为末尾元素,第二重(j)搜索比a[i]小的元素,如果符合要求,更新f[i]。
第一层循环是无法优化的,但是我们可以优化第二层,不再逐个搜索。
设当前上升子序列的长度为k,末尾元素有x0<y0<z0可供选择,那么为了让后面的数尽可能多的加入到序列中,我们要取最小的x0,这样y0,z0便都可以入队了。否则一开始就选y0或z0,那么比它小的便不能再入队,这样不是最优解。
所以,对于f[i]的每一个取值k,我们只需要保留满足F[t] = k的所有A[t]中的最小值。
并且,随着k的递增,这个最小值也是递增的(因为大数一定会放在后面)。
由此得出nlog(n)的算法:
开一个数组d记录长度为i的子序列最后一个数的最小取值。
设当前求出的最长子序列的长度为 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数组有单调性,我们在d中查找时,可以使用二分,则整个算法的时间复杂度下降为O(nlogn),有了非常显著的提高。需要注意的是,d在算法结束后记录的并不是一个符合题意的最长上升子序列,只是各个长度的子序列末尾元素的最小值。
对于二分寻找,c++党有福利啦,可以用lower_bound()返回比a[i]小的最大一个值后面的位置,直接插入就可以了。当然,手写也不是很难。
代码:
#include<iostream>
#include<cstdio>
using namespace std;
int n,a[300000],d[300000],i,j,len,k;
int findg(int lenn,int n)
{
int left=0,right=lenn,mid=(left+right)/2;
while(left<=right)
{
if(n>d[mid]) left=mid+1;
else if(n<d[mid]) right=mid-1;
else return mid;
mid=(left+right)/2;
}
return left;
}
int main()
{
freopen("problem.in","r",stdin);
freopen("problem.out","w",stdout);
cin>>n>>k;
for ( i=1;i<=n;i++) cin>>a[i];
d[0]=-1;
d[1]=a[0];
len=0;
for ( i=1;i<=n;i++)
{
if ((i>k&&a[i]>a[k])||(i<k&&a[i]<a[k]))
{
j=findg(len,a[i]);
// j=lower_bound(d+1,d+len+1,a[i])-d; // 也可以用这句话省去二分过程
d[j]=a[i];
if(j>len)
len=j;
}
}
cout<<len+1;
return 0;
}
关于lower_bound():
http://blog.csdn.net/niushuai666/article/details/6734403
upper_bound():http://blog.csdn.net/niushuai666/article/details/6734650