处理何种问题:已知一个原序列,求出其最长的一个子序列,且该子序列是单调递增的。(对于其子序列的要求条件是,可以不连续,但是先后顺序不可改变)
性能:普通DP为O(n^2),贪心+二分为O(n*logn),DP+树状数组O(n*logn)。
原理: 贪心+二分解释不出原理,网上的资料个人感觉不是很有说服力; DP类的状态转移方程为: dp[i]=max{dp[j]+1}(1<=j<i,arr[j]<arr[i])
实现步骤:在这里写一下后两种算法的。
贪心+二分:对于一个上升的子序列,显然其结尾元素越小,越有利于在后面接其他的元素,也就越有可能变得更长。开一个数组arr,用于存放原始数列,依次取出每一个数,放入一个是原始是空的有序数组path里面,用lower_bound();求出该数在path里可以刚好替换原位置,且path依旧可以保持升序的位置,例如:
原序列为1,5,8,3,6,7
Path已为1,5,8,此时读到3,用3替换5,得到1,3,8; 再读6,用6替换8,得到1,3,6;再读7,得到最终path为1,3,6,7。最长递增子序列为长度4。
该方法我也只能理解到这里了,每个数在path里的位置,个人感觉就是dp[i],但是这种解法有点绕,严谨的证明逻辑还得自己以后想。
DP+树状数组维护:这个方法我喜欢讲
状态转移方程为: dp[i]=max{dp[j]+1}(1<=j<i,arr[j]<arr[i])
这个方程比较好想出来,但是有一点不好处理,就是在1~i里面找最大的dp[j],且arr[j]<arr[i],区间找最大值见过,线段树嘛,但是有限制条件的找最大值我就没见过了。怎么做呢?先将原始序列从小到大排序,并且保存好原数列的角标,然后对于每一个数边查找,边入树,比如第一个数的值是1,位置是100,因为原始dp数组里的值都是0,所以在dp[1~99]里找到的最大值就是0,所以dp[100]=1,然后将dp[100~n]的最大值更新一遍(树状数组更新),然后再进行第二个数,假设是5,位置是120,步骤还是和上面一样,之所以这么做的原因是,因为之前排序之后已经处理掉arr[i]>arr[j]的这个条件了,然后再用边查找,边枚举的方法,就可以很好的处理掉这个问题了。有些算法原理就是自然而然,不用纠结,记住这种处理策略就好。
备注:重点把这种处理最大值的方法记住。
输入样例解释:
8 //n个数字
9 5 6 9 2 15 2 5
输出样例解释:
4 //最长上升子序列的长度
//LIS 有三种方法求解,第一种是常规 DP,时间复杂度是O(n^2);第二种是贪心+二分 时间复杂度是O(nlogn); //第三种是DP树状数组维护,时间复杂度是O(nlogn)。在这里依次写下来: /* #include<iostream> #include<cstdio> #include<algorithm> #include<string.h> using namespace std; const int MaxN=10010;//数据量最大 int arr[MaxN],dp[MaxN]; int main() { int n; while(~scanf("%d",&n)) { for(int i=1;i<=n;++i) scanf("%d",&arr[i]); for(int i=1;i<=n;++i) dp[i]=1;//注意:dp初始值是1,因为每个数都可以选取自身 for(int i=1;i<=n;++i) { for(int j=1;j<i;++j) { if(arr[i]>arr[j]) dp[i]=max(dp[i],dp[j]+1); } } int ans=0; for(int i=1;i<=n;++i) ans=max(ans,dp[i]); printf("%d ",ans); } return 0; } */ /* #include<iostream> #include<cstdio> #include<algorithm> #include<string.h> using namespace std; const int MaxN=10000000;//大致的极限空间复杂度,同时也大概是极限时间复杂度 int arr[MaxN]; int path[MaxN];//贪心时所用到的辅助数组 int main() { int n; while(~scanf("%d",&n)) { memset(arr,0,sizeof(arr)); memset(path,0,sizeof(path)); for(int i=1;i<=n;++i) scanf("%d",&arr[i]); int tot=0; for(int i=1;i<=n;++i) { int num=lower_bound(path+1,path+tot+1,arr[i])-path; path[num]=arr[i]; tot=max(tot,num); } printf("%d ",tot); } return 0; } */ #include<iostream> #include<cstdio> #include<algorithm> using namespace std; #define lowbit(x) (x&(-x)) const int MaxN=10000; int n; struct node { int val,num; }; node arr[MaxN]; int dp[MaxN]; bool cmp(node a,node b) { if(a.val<b.val||(a.val==b.val&&a.num<b.num)) return 1; return 0; } int modify(int x,int y) { for(; x<=n; x+=lowbit(x)) dp[x]=max(dp[x],y); } int query(int x) { int res=0; for(; x; x-=lowbit(x)) res=max(res,dp[x]); return res; } int main() { int ans; while(~scanf("%d",&n)) { ans=0; for(int i=1; i<=n; ++i) { scanf("%d",&arr[i].val); arr[i].num=i; dp[i]=0; } sort(arr+1,arr+n+1,cmp); for(int i=1; i<=n; ++i) { int maxx=query(arr[i].num); modify(arr[i].num,++maxx); ans=max(ans,maxx); } printf("%d ",ans); } return 0; }