再谈升/降序子序列——POJ1631

来源:互联网 发布:淘宝达人和微淘 编辑:程序博客网 时间:2024/04/28 00:49

上一篇文章介绍了如何求解最长升/降序子序列的长度,这篇文章讨论另一个与升/降序子序列有关的问题。

问题:将一个序列划分成单调的子序列最少可划分成多少个?

例如。序列:1、4、2最少可划分成2个单调递增的子序列:{1,2}、{4}或{1,4}、{2};

乍一看,觉得这需要动规,其实这可以贪心。

在用动规求序列{1、4、2}的时候,我们要考虑的一个重要问题是,1是与4连接还是与2连接,

因为这种选择会影响到后面我们能获得的子序列的长度。但是现在我们并不需要求最长子序列

的长度是多少,我们要求的是序列的个数。

其实1与4连接或与2连接都会得到同样多的子序列,所以我们可以设计一个如下的算法:

queue q;

for(i:序的第一元素 to 序列的最后一个元素) {

for(k:队列的一个元素 to 队列的最后一个元素) 

if(队列元素k<序列元素i)  { k=i; break(); }

if(队列中没有比当前元素小的元素) 将当前元素插入队列;

}

最后队列的长度值就是子序列的个数。(使用二分后时间复杂度为O(nlgn))


POJ 1631:http://poj.org/problem?id=1631;


#include<cstdio>#include<algorithm>using namespace std;int arr[100100],marr[100100];int bs(int s,int e,int k){    int mid=(s+e)>>1;    if(marr[mid]<=k && mid>=e) return mid;    if(marr[mid]<=k && k<marr[mid+1]) return mid;    if(k<marr[mid]) return bs(s,mid-1,k);    else return bs(mid+1,e,k);}int main(){    //freopen("1631.in","r",stdin);    int t,p;    scanf("%d",&t);    while(t--) {         scanf("%d",&p);         for(int i=1;i<=p;i++) scanf("%d",&arr[i]);         marr[0]=arr[1];         for(int i=1;i<=p;i++) if(arr[i]<marr[0]) marr[0]=arr[i]-1;         int len=0;         for(int i=1;i<=p;i++) {              int idx=bs(0,len,arr[i]);              marr[idx+1]=arr[i];              if(idx+1>len) len=idx+1;         }         printf("%d\n",len);    }    return 0;}