思路:非常明显而且经典的最长下降子序列的题目,题目中又说明了每个数值大小不同更是降低了难度。 题目要求分为两部分: 1.求最长下降子序列 2.求需要几串下降的子序列才能包含全串
因为我们在题目中看到数值不超过30000那么通常的dp用O(N^2)的方法是会超时的!那么我们需要优化一下。思路就是贪心+二分查找
求一个序列的最长上升子序列,可以用贪心的思想来做。
例:389 207 155 300 299 170 158 65
要求它的最长递减子序列,具体步骤如下:
389
389 207
389 207 155
389 300 155 //207被300替换了,207是三个数中刚好小于300的数,207能接的后序列,300照样能接,而且有更大的空间可以接
389 300 299 //此次用299替换155,一样是因为有更大的空间可用,在这里就体现了用300来替换207的正确性
389 300 299 170
389 300 299 170 158
389 300 299 170 158 65
这样就得到了一个最长递减子序列,而且更有可能接更多的数 最后这个串的长度就是最大的长度! 我们假定变化的数组为X,源数据数组为Y,那么假定Y[i]比X当前最小值还小,那么加到X的后面,否则二分查找X串中比Y[i]小的最大的值,并替换成Y[i]。具体如上!
事实上这个继续用贪心就可以了,但是需要结合队列用。 前提是我们要求队列数,假定一个下降子序列为一队,只要一队一队去掉,然后统计有几队即可!而且,不需要每一队是最优的!因为只要出现一个值比前一个值大(顺序对),那么就必须再有一个队,比如说 8 7 6 3 5 4 2 1 那么一定是2队,可以使8 7 6 3 2 1+5 4(5比前面的3大) 并不需要是每队最优的。(说不太清,自己体会一下吧) 那么实现的时候我们可以用队列,每一轮摘取出一队,递减的数直接pop掉,出现比前面大的情况就压到队列的尾部。知道所有的队都被提取了出来! 哎,说不太清,直接看代码吧…5_5
#include<bits/stdc++.h> using namespace std; int value[30001],length=0; int getmax[30001],maxlength,maxpos; //maxlength表示下降子序列的最大长度 ,讲解中的X串 void dfs(int start,int end,int temp){ //二分查找 if(start>end) return; int mid=(start+end)/2; if(getmax[mid]<temp){ maxpos=mid; //比Y[i]小的最大的值的位置 dfs(start,mid-1,temp); //继续向前搜索 } else{ dfs(mid+1,end,temp); } } int main(){ //freopen("input.txt","r",stdin); while(scanf("%d",&value[++length])!=EOF); //输入,真实的长度为length-1,自己试一下 getmax[1]=value[1]; maxlength=1; for(int i=2;i<length;i++){ if(value[i]<getmax[maxlength]){ //如果Y[i]比X当前最小值还小 getmax[++maxlength]=value[i]; //那么Y[i]加到X的后面 } else{ dfs(1,maxlength,value[i]); //否则二分查找X串中比Y[i]小的最大的值,并替换成Y[i] getmax[maxpos]=value[i]; } } cout<<maxlength<<endl; length--; //更正length Y的长度 queue<int>q; //用队列实现lines数的统计 int lines=0,maxtemp,len; // maxtemp代表每队当前的最小值 for(int i=1;i<=length;i++){ q.push(value[i]); } while(!q.empty()){ maxtemp=q.front(); len=length; //每次初始化当前queue的长度 for(int t=1;t<=len;t++){ if(q.front()<=maxtemp){ maxtemp=q.front(); q.pop(); length--; //更新length长度 } else{ q.push(q.front()); //如果出现顺序对那么压回队列 q.pop(); } } lines++; //统计队数 } cout<<lines<<endl; return 0; }