POJ2533_Longest Ordered Subsequence (线性动态规划变形)
本题求一个字符串中的最长递增子序列的长度。
动态规划方程
a[]记录字符串;
d[i]记录以第i个元素为最后一个元素的最长递增序列的长度
则 d[i+1]=1+max(d[j]) 其中(j<i+1)并且a[j]<a[i+1]。
这样的话,没更新一个d[i+1],都需要搜索一遍前i项,因而此时复杂度为o(n^2)。
******* 疯狂的分割线 ********
这样的效率显然太低了。通常的想法是能不能借助二分查找优化复杂度至o(nlogn)。
而直接进行二分查找显然不现实(因为无序)
因此转换思路,设置一个数组s[]用于记录 : s[k]为满足d[i]=k的最小的a[i] 。(天才的一步)
(关于这个s[k]还有一种解释方式,后面再说)
这样,s的下标实质上就保存了最长递增序列的长度,同时由于每访问一个新的a[i]就会尝试在s数组中插入该a[i],因而s[k]就始终是保持有序的。
啧,如果感觉上述解释不够清楚的话,另一种解释方式如下:
本质上一个长度为n的字符串的最大递增子序列的是有限的也就是 [1,n]。因此不妨采用多阶段决策的方式分别考虑每一种长度和他们的递增关系。
因此设置s[]数组来记录 :例如 记最大递增子序列长度为1的子序列的最后一个元素为s[1]; 记最大递增子序列长度为2的子序列的最后一个元素为s[2];s[k]同理。
这样只需遍历一遍a[]字符串,对其中的每一个字符都对s[]做一个更新维护:维护的原则对于a[i]是找到当前s[]中比a[i]小的最大数的后一个位置,该位置就是a[i]应插入的位置,因为该位置插入后可以保证s依然有序,同时所覆盖的原来的值也一定是比a[i]大的。
过程模拟如下:
对于a[]={1,2,4,3}
s初始状态为{-1,inf};
开始遍历a数组;
a[0]=1,二分查找后得到1应该插入s[1]位置;s状态变为{-1,1,inf},s[1]=1;(注意s的定义)
a[1]=2,二分插入后s状态变为(-1,1,2,inf),s[2]=2;
a[4]=4,二分插入后s状态变为{-1,1,2,4,inf},s[3]=4;
a[3]=3, 此时2<3<4,因此二分查找到的位置为当前4的位置,s状态变为{-1,1,2,3,inf},此时s[3]=3;
此时a数组遍历完成,s数组的最大长度为3,也就是所要求的结果。(s数组有一种贪心的意思在里面,每更新一次都保证其值是最小的尾数)
AC代码如下:
#include<iostream> #include<cstdio> #include<cstdlib> using namespace std; const int maxn=1000+5; const int inf=10001; int a[maxn]; int s[maxn]; int b_search(int *s,int x,int len){//左闭右开 int l=0,r=len; while(l<r){ int mid=l+(r-l)/2; if(s[mid]==x)return mid; else if(x<s[mid]){ r=mid; } else if(x>s[mid]){ l=mid+1; } } return l; } int main(void){ int n; cin>>n; for(int i=0;i<n;i++) { scanf("%d",&a[i]); } s[0]=-1; int len=1; for(int i=0;i<n;i++){ s[len]=inf; int j=b_search(s,a[i],len+1); if(j==len)len++; s[j]=a[i]; } int ans=len-1; cout<<ans<<endl; return 0; }