LIS——最长上升子序列
处理何种问题:已知一个原序列,求出其最长的一个子序列,且该子序列是单调递增的。(对于其子序列的要求条件是,可以不连续,但是先后顺序不可改变)
性能:普通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\n",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\n",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\n",ans);
}
return 0;
}

浙公网安备 33010602011771号