最长上升子序列的做法和一些例题
今天听学长讲课学到了新的解决LIS问题的做法。做个记录
1:
考虑 $ O(n^2) $ dp处理。
设 \(f_i\) 为当前第 \(i\) 个的最长长度,则自然有 $ f_i=\max\limits_{j=1}^{i-1}f_j +1 ,a_i>a_j $
2:
考虑将新加入的序列放入栈中。如果当前的\(a_i\)是上升的,就直接放入栈中。否则在栈中二分,找到第一个大于等于的元素并替换。
要注意,这个做法并不能找到原序列,只能求出长度。
原理是贪心,二分将较小的元素替换,实际操作时会更有机会加入更多数字。如果原序列没有这个元素也不影响,序列的长度是没有改变的。
可以手模样例思考一下。
时间是\(O(nlogn)\)
Code:
for(int i=1;i<=n;i++){
if(a[i]>s[top])s[++top]=a[i];
else{
int l=1,r=top;
while(l<r){
int mid=(l+r)>>1;
if(s[mid]>=a[i])r=mid;
else l=mid+1;
}
s[l]=a[i];
}
}
printf("%d\n",top);
3:
考虑在#1中的基础上在设立一个g,
\(g_i\)表示已经处理的所有\(f\)中,\(f_j=i\) 中最小的\(a_j\)。也就是\(f\)答案为\(i\)且结尾最小的值。
并且可以发现,\(g_i\)是单调递增的。可以举个反例,如果出现了下降,则一定存在可以更新前面值的情况(长度更长,但结尾值更小)
于是每次处理在\(g\)数组上二分查找,取最大的\(i\)。
这样储存也避免了用权值线段树/树状数组处理。
当然,你仍然可以离散化后用权值线段树/树状数组处理。做法与这个类似,不写代码了。
时间是\(O(nlogn)\)
Code:
memset(g,0x3f,sizeof(g));
f[1]=1,g[1]=a[1];
for(int i=2;i<=n;i++){
int l=1,r=i-1;
while(l<r){
int mid=(l+r+1)>>1;
if(g[mid]<a[i])l=mid;
else r=mid-1;
}
if(g[l]<a[i]){
f[i]=l+1;
}
else f[i]=1;
g[f[i]]=min(g[f[i]],a[i]);
}
int ans=0;
for(int i=1;i<=n;i++)ans=max(ans,f[i]);
printf("%d\n",ans);