【DP】vijos P1369 难解的问题

题目链接: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


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值