动态规划:单调减子序列

来源:互联网 发布:什么软件可以透视 编辑:程序博客网 时间:2024/06/05 23:01

一个算法,题目如下:

        从一个由N个整数排列组成的整数序列中,自左向右不连续的选出一组整数,可以组成一个单调减小的子序列(如从{68 69 54 64 68 64 70 67 78 62 98 87}中我们可以选取出{69 68 64 62}这个子序列;当然,这里还有很多其他符合条件的子序列)。给定整数序列的长度和整数序列中依次的值,请你求出这个整数序列中“最长的单调减小的子序列的长度”以及“不同但长度都是最长得单调减小的子序列的数量”。

       输入第1行为一个整数N,表示输入的整数序列的长度(1≤N≤50000)。输入第2行包括由空格分隔的N个整数(每个整数都在32位长整型范围内)。
输出包括一行,为两个数字,分别为针对给定的整数序列求出的“最长的单调减小的子序列的长度”以及“值不同但长度都是最长得单调减小的子序列的数量”
样例输入
12
68 69 54 64 68 64 70 67 78 62 98 87
样例输出
4 2

分析:

        求序列中的最长单调递增或递减序列长度的动态规划算法网上到处都是,基本思想就是把之前不同长度的最长子序列的最大/小最后一个数给记下来,然后依次的更新后面的数,更新时采用二分查找可以提高效率,缩小复杂度。可以自己先想一下怎么实现,不要直接去看代码,会被搞得云里雾绕的,一些文章写得不是很清晰,代码质量也一般,所以我一般都是先想一下自己会怎么实现,然后再看别人的讲解,代码其次,因为有时候看别人代码,“就跟被强x了似的”(别人说的)。

       但本题目同时要求出最长子序列的数量,这就不一样了,比如这个序列,4 5 2 1,最长递减子序列长度是3,但4 2 1和5 2 1都是最长递减子序列,个数为2.再比如2 1 2 1最长递减子序列长度是2,虽有两个2 1子序列,但只能算1个,因为子序列相同。

       因为一开始没有理解题意,认为2 1 2 1这种情况只要选取子序列时的位置不同就算一个子序列,那么此时就会算有3个最长子序列,虽然内容相同,都是2 1,但选取的元素的位置不同,耗费了大量时间,把代码搞得很复杂,当然出题人也可以这么设计题目。本题中题目意思经测试不是这个意思,2 1 2 1只能算有1个长度为2的内容为2 1的最长子序列。

       现在我们知道了题意,就可以找几个例子来设计一下算法。因为求递减子序列,我的想法是从最后一位进行处理,每次处理时找出以本数为起始时的最长子序列长度及个数,然后不断的往序列前面处理,直到序列处理完毕,然后统计出最长的子序列个数。关键算法在于找出以本数为起始时的最长子序列长度及内容不同最长子序列个数,根据动态规划的思想,将以每个数为起始的长度、下标记录,然后处理序列前面的数时,拿这个数与之前记录中的每个下标位置的数进行比较,求出若能构成最长的子序列,若构成最长子序列的个数不是1个,那么有几个就记录几个长度和下标,此处我用了multimap<int,int>来进行存储长度和下标键值对,因为multimap允许重复键存在。

       找个特殊一点的例子进行举例分析,比如4 3 2 5 3 2这个序列,从后往前处理,最后一位为2,最长子序列长度位1,存储长度1、数组下标5;那么插入<1,5>;然后是3,根据存储内容,插入一个<2,4>,此时multimap为:<1,5> <2,4>;然后是5,插入<3,3>,此时multimap为:<1,5> <2,4> <3,3>;接下来处理2,因为之前有了2这个长度为1的子序列,且与之前的2之间不存在比2小的数,所以没有产生更长的子序列,所以这里不必记录,multimap不变;然后是3,同样可以查出之前有3存在,且与之前的2之间multimap中没有长度为1的子序列小的数,所以也不记录,multimap不变;然后是4,可以从multimap中搜索出长度为2的<2,4>,所以插入<3,0>,此时multimap为:<1,5> <2,4> <3,3> <3,0>,然后统计键最大为3,数量为2,即完成统计。

       再举个例子,6 6 4 3 2 5 3 2这个 序列,比上个例子前面多了两个6,沿着上面例子往下处理6,此时会发现multimap(<1,7> <2,6>  <3,5> <3,2>)有<3,5> <3,2>两个对应的子序列都可以与6构成长度为4的子序列,此时怎么办呢,直接说吧,就是插入两个<4,2>,此时multimap为<1,7> <2,6>  <3,5> <3,2> <4,2> <4,2>;然后处理第一个6,此时发现multimap中记录有6开始的最长子序列为4,且搜索发现两个6之间不存在长度大于3的最长子序列(这种搜索通过搜索multimap就可实现),所以不用记录。这样,统计键值最大为4,数量有2个,所以借出答案。

       哎,之前没有用过multimap,被其迭代器搞得头都大了,耗费了很长时间,理解题意也耗费了很长时间,晕死,然而这不是最重要的,最重要的是我终于通过了所有测试点后,提交时给出了超时的结果,脑浆崩裂,觉得优化的话只能从“关键算法在于找出以本数为起始时的最长子序列长度及内容不同最长子序列个数“入手,但是multimap应该搜索效率不低啊,红黑树的插入、搜索复杂度O(LogN)应该与二分不相上下啊,这可怎么整,网上搜索也搜不到啊,读者谁知道告诉下,感激。

代码:

#include <iostream>#include <stdlib.h>#include <map>#define DEBUG 1// 数据测试估计已通过,但算法超时。int main(){using namespace std;int Count;int iQueue[50000];cin>>Count;for(int i=0; i<Count; i++)cin>>iQueue[i];typedef pair<int,int> pair_subQueue;multimap<int,int> map_subQueue;multimap<int,int>::reverse_iterator iter,iter_sameKey;int minNum,maxNum;minNum=iQueue[Count-1];maxNum=iQueue[Count-1];map_subQueue.clear();map_subQueue.insert(pair_subQueue(1,Count-1));#ifdefDEBUGcout<<"insert0:"<<1<<" "<<Count-1<<endl;#endiffor(int i=Count-2; i>=0; i--){if(iQueue[i]>maxNum)// 本数值最大的话子序列长度肯定为之前最长子序列加1;{maxNum=iQueue[i];//iter=map_subQueue.rend();int length=(map_subQueue.rbegin())->first;int numSamekey=map_subQueue.count(length);for(int j=0; j<numSamekey; j++){map_subQueue.insert(pair_subQueue(length+1,i));#ifdefDEBUGcout<<"insert1:"<<length+1<<" "<<i<<endl;#endif}}else if(iQueue[i]<minNum)// 本数值最小的话子序列长度为1;{map_subQueue.insert(pair_subQueue(1,i));minNum=iQueue[i];#ifdefDEBUGcout<<"insert2:1"<<" "<<i<<endl;#endif}else{int length;for(iter=map_subQueue.rbegin(); iter!=map_subQueue.rend(); iter++)// 找到以本数字开头最长的子序列;{if(iQueue[i]>iQueue[iter->second]){length=iter->first;int numSubQueue(0);while(iter->first==length && iter!=map_subQueue.rend()){if(iQueue[i]>iQueue[iter->second])// 先统计能构成多少个最长子序列;numSubQueue++;iter++;if(iter==map_subQueue.rend())break;}for(int j=0; j<numSubQueue; j++)// 插入;{map_subQueue.insert(pair_subQueue(length+1,i));#ifdefDEBUGcout<<"insert3:"<<length<<" "<<i<<endl;#endif}break;// 插入完毕退出,处理序列中下一个数;}if(iQueue[i]==iQueue[iter->second]&&iter!=map_subQueue.rend()){int pos(Count-1);length=iter->first;while(iter->first==length && iter!=map_subQueue.rend())// 找到序列中最近相同数值的下标;{if(iQueue[i]==iQueue[iter->second]&&iter->second<pos)pos=iter->second;iter++;if(iter==map_subQueue.rend())break;}int numSubQueue(0);while(iter->first==length-1 && iter!=map_subQueue.rend()){if(iter->second<pos)// 统计是否两个相同数字间存在最长子序列;numSubQueue++;iter++;if(iter==map_subQueue.rend())break;}for(int j=0; j<numSubQueue; j++){map_subQueue.insert(pair_subQueue(length,i));// 存在就记录;#ifdefDEBUGcout<<"insert4:"<<length<<" "<<i<<endl;#endif}break;}}}}int iLength=(map_subQueue.rbegin())->first;int numSubQueue=map_subQueue.count(iLength);cout<<iLength<<" "<<numSubQueue<<endl;return 0;}

       在这里,讨论一下如果题目是另外一种要求的情况,即最长子序列可以内容相同,但选取的位置不同就算不同的子序列算法实现,即2 1 2 1这种例子就会有3个长度为2,内容都为2 1的最长子序列。大的步骤类似以上分析,主要区别在于当出现相同的数值时,也要记录最长子序列信息。代码如下:

#include <iostream>#include <stdlib.h>#include <map>#define DEBUG 1int main(){using namespace std;int Count;int iQueue[50000];cin>>Count;for(int i=0; i<Count; i++)cin>>iQueue[i];typedef pair<int,int> pair_subQueue;multimap<int,int> map_subQueue;multimap<int,int>::reverse_iterator iter,iter_sameKey;int minNum,maxNum;minNum=iQueue[Count-1];maxNum=iQueue[Count-1];map_subQueue.clear();map_subQueue.insert(pair_subQueue(1,Count-1));#ifdefDEBUGcout<<"insert0:"<<1<<" "<<Count-1<<endl;#endiffor(int i=Count-2; i>=0; i--){if(iQueue[i]>maxNum)// 本数值最大的话子序列长度肯定为之前最长子序列加1;{maxNum=iQueue[i];//iter=map_subQueue.rend();int length=(map_subQueue.rbegin())->first;int numSamekey=map_subQueue.count(length);for(int j=0; j<numSamekey; j++){map_subQueue.insert(pair_subQueue(length+1,i));#ifdefDEBUGcout<<"insert1:"<<length+1<<" "<<i<<endl;#endif}}else if(iQueue[i]<minNum)// 本数值最小的话子序列长度为1;{map_subQueue.insert(pair_subQueue(1,i));minNum=iQueue[i];#ifdefDEBUGcout<<"insert2:1"<<" "<<i<<endl;#endif}else{int length;for(iter=map_subQueue.rbegin(); iter!=map_subQueue.rend(); iter++)// 找到以本数字开头最长的子序列;{if(iQueue[i]>iQueue[iter->second]){length=iter->first;int numSubQueue(0);while(iter->first==length && iter!=map_subQueue.rend()){if(iQueue[i]>iQueue[iter->second])numSubQueue++;iter++;if(iter==map_subQueue.rend())break;}for(int j=0; j<numSubQueue; j++){map_subQueue.insert(pair_subQueue(length+1,i));#ifdefDEBUGcout<<"insert3:"<<length<<" "<<i<<endl;#endif}break;}if(iQueue[i]==iQueue[iter->second]&&iter!=map_subQueue.rend()){length=iter->first;iter_sameKey=iter;int pos(Count-1);bool isHaveLongerQueue(false);while(iter_sameKey->first==length&&iter_sameKey!=map_subQueue.rend()){// 检测是否可能有更长的子序列;if(iQueue[i]>iQueue[iter_sameKey->second]){isHaveLongerQueue=true;break;}if(iter_sameKey->second<pos)pos=iter_sameKey->second;iter_sameKey++;if(iter_sameKey==map_subQueue.rend())break;}if(isHaveLongerQueue){int numSubQueue(0);while(iter->first==length&&iter!=map_subQueue.rend())// 有的话,就要记录更长的子序列{if(iQueue[i]>iQueue[iter->second])numSubQueue++;iter++;if(iter==map_subQueue.rend())break;}for(int j=0; j<numSubQueue; j++){map_subQueue.insert(pair_subQueue(length,i));#ifdefDEBUGcout<<"insert4:"<<length<<" "<<i<<endl;#endif}break;}else// 没有的话,记录等长的子序列,分两种情况检测;{if(length==1)// 情况1:检测处理长度length子序列;{map_subQueue.insert(pair_subQueue(1,i));#ifdefDEBUGcout<<"insert5:"<<length<<" "<<i<<endl;#endifbreak;}if(length!=1)// 情况1:检测处理长度length子序列;{int numSamekey2(0);while(iter->first==length&&iter!=map_subQueue.rend())// 先统计数量;{if(iter->second==pos)numSamekey2++;iter++;if(iter==map_subQueue.rend())break;}for(int j=0; j<numSamekey2; j++)// 再统一插入;{map_subQueue.insert(pair_subQueue(length,i));#ifdefDEBUGcout<<"insert6:"<<length<<" "<<i<<endl;#endif}}int numVice(0);  // 情况2:检测处理长度length-1子序列;while(iter_sameKey->first==length-1&&iter_sameKey!=map_subQueue.rend()){if(iQueue[i]>iQueue[iter_sameKey->second]&&iter_sameKey->second<pos)numVice++; // 这种情况下能构成最大子序列;iter_sameKey++;if(iter_sameKey==map_subQueue.rend())break;}for(int j=0; j<numVice; j++){map_subQueue.insert(pair_subQueue(length,i));#ifdefDEBUGcout<<"insert7:"<<length<<" "<<i<<endl;#endif}break;}break;}}}}int iLength=(map_subQueue.rbegin())->first;int numSubQueue=map_subQueue.count(iLength);cout<<iLength<<" "<<numSubQueue<<endl;return 0;}


 

 

0 0
原创粉丝点击