Description
给定一个数列,求最长上升子序列长度
Input
多组测试数据,每组数据给出1 ≤ n ≤ 10^3和1 ≤ b ≤ 10^4,表示n个数a1, a2, …, an.
其中 a1 = b,ai = (ai − 1+1)2mod(10^9+7), i > 1.
Output
最长上升子序列长度.
Sample Input
10 5
Sample Output
8
Hint
获得数组参考代码:
a[1] = b;
for(int i = 2; i <= n; i ++) {
a[i] = 1LL * (a[i - 1] + 1) * (a[i - 1] + 1) % mod;
}
该题属于动态规划算法练习里的一道题
首先我们需要明白什么时动态规划,说白了动态规划的思想就是将我们面对的问题一次一次地划分为比原先问题规模更小的子问题,此外我们需要保证每一个子问题的结构是相同的,最终得到最小子问题,我们再递推回原先的问题即可得到问题的解
对于该题而言,我们首先需要一个数组来记录子问题的解(一般题目问什么,数组的定义按照问题来定义即可),这里我们定义一维数组dp[i]为到下标为i的元素前的最长上升子序列长度为dp[i]
然后,我们需要清楚最小子问题,以及各个子问题之间的关系(即得到递推方程)
很明显,最小子问题就是dp[1]=1,即当只有一个元素时,最长上升子序列长度为1
而子问题之间的关系我们需要仔细理解题意来得到
对于该题而言,为了保证上升子序列尽可能的长,那么就有 dp[ i ] 尽可能的大, 但是再保证 dp[ i ] 尽可能大的基础上,还必须满足序列的上升; 所以呢 dp[ i ] = max ( 1 , dp[ j ] + 1 ) { j < i && aj < ai }
这里的1就是当 ai 前面的数都比他大的时候,他自己为一个子序列;dp[ j ] + 1 指的是: 当第 i 个数前面有一个 第 j 个数满足 aj < ai 并且 j < i 这时候就说明 ai 元素可以承接在 aj 元素后面来尽可能的增加子序列的长度
据此,即可完成代码
#include<cstdio> #include<algorithm> #include<climits> using namespace std; int mod=1e9+7; int a[1010]; int n,b; int dp[1010];//定义dp[i]为以i为下标的a[i]为结尾的最长上升子序列长度 //最小子问题:当a[i]前面的数都大于它时,此时最长上升子序列为1 //否则,选择a[i]前面的子问题dp[j]连接上当前的a[i],即dp[j]+1 int main(){ while(scanf("%d%d",&n,&b)!=EOF){ int ans=1; a[1] = b; for(int i = 2; i <= n; i ++) { a[i] = 1LL * (a[i - 1] + 1) * (a[i - 1] + 1) % mod; dp[i]=1;//初始化(假设每一个数前面的数都比他大) } dp[1]=1; for(int i=2;i<=n;i++){ for(int j=1;j<i;j++){ if(a[i]>a[j]){ dp[i]=max(dp[j]+1,dp[i]); }//4 5 1 2 3 } ans=max(ans,dp[i]);//得到最大上升子序列 } printf("%d\n",ans); } return 0; }
容易得到上面的时间复杂度是O(n^2)
下面我们将数据改变为1 ≤ n ≤ 10^6和1 ≤ b ≤ 10^4
那么对于O(n^2)的算法即无法通过了
下面我们介绍使用贪心算法的O(nlogn)
根据题意,我们需要得到最长的上升子序列,那么怎样才能得到最长呢,肯定每一个数字越小越长嘛,因此我们定义一个数组来存储最长的上升子序列,每次从输入数组中读入元素ele,如果说ele大于已经得到的子序列的最大元素,则直接加入即可,反之,我们从子序列中找到第一个大于ele的元素并将其替换为ele(选择使用二分查找)
据此我们即可完成代码
#include<cstdio> #include<algorithm> #include<climits> using namespace std; int mod=1e9+7; int a[1000010]; int n,b; int listt[1000010];//定义listt[i]为我们的贪心序列,即每次选择a数组中最小的元素放到listt中 //选择最小元素才能使得我们的上升子序列最长 int find(int beg,int endd,int ele){//使用二分查找在listt数组的beg到endd(左闭右闭)中第一个大于ele的元素 while(beg<endd){ int mid=(beg+endd)/2; if(listt[mid]<=ele){ beg=mid+1; }else{ endd=mid; } } return endd; } int main(){ while(scanf("%d%d",&n,&b)!=EOF){ int ans=1; a[1] = b; for(int i = 2; i <= n; i ++) { a[i] = 1LL * (a[i - 1] + 1) * (a[i - 1] + 1) % mod; } listt[ans]=a[1];//第一个元素直接加入 for(int i=2;i<=n;i++){ if(a[i]>listt[ans]){ listt[++ans]=a[i];//如果a[i]大于贪心序列末尾元素 }else{//否则寻找第一个大于a[i]的元素将其替换为a[i] int index=find(1,ans,a[i]); listt[index]=a[i]; } } printf("%d\n",ans);//输出listt长度即可 } return 0; }