最长上升子序列LIS
怎么求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(n \log n)\)
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\)。
这样储存也避免了用权值线段树/树状数组处理。
当然,你仍然可以离散化后用权值线段树/树状数组处理。做法与这个类似,不写代码了。
可以记录转移方向,找到原来的LIS。
时间是\(O(n \log n)\)
Code:
memset(g,0x3f,sizeof(g));
for(int i=1;i<=n;i++){
int l=0,r=i-1;
while(l<r){
int mid=(l+r+1)/2;
if(g[mid]<a[i])l=mid;
else r=mid-1;
}
f[i]=l+1;
g[f[i]]=a[i];
}
int ans=0;
for(int i=1;i<=n;i++)ans=max(ans,f[i]);
printf("%d\n",ans);
例#1:CF650D
给定一个长度为 \(n\) 的序列 \(h_1,h_2,h_3,\dots,h_n\) 以及 \(m\) 个操作,每个操作形如 a b,表示将序列中第 \(a\) 个数改为 \(b\)。
对于每个操作,求出序列修改后的最长严格上升子序列长度。
注意:每个操作之间彼此独立。即每次操作未进行时的序列是输入时的原序列,而不是上一次操作后得到的序列。
\(1\leqslant n, m \leqslant 4\times 10^5\)。
修改数组中的一个数,会造成什么影响?
可能有两种结果,一是 \(a_x\) 不再作为LIS中的一个,二是 \(a_x\) 仍在LIS中。
假如 \(a_x\) 仍在LIS中。可以将要求的序列分为两截,左边是末尾大小小于 \(w\) 的LIS,右边是开头大于 \(w\) 的LIS。可以设 \(f_i\) 表示从左向右的最长上升子序列,\(g_i\) 是从右向左的最长下降子序列。(这样就是以 \(i\) 开头的LIS了)
分别正序和逆序处理 \(f_i\) 和 \(g_i\) 。处理到第 \(x\) 位时,要找到左侧末尾小于 \(w\) 的LIS。可以设 \(fx_i\) 表示LIS长度为 \(i\) 时最小以 \(fx_i\) 结尾。这么设是因为可以发现 \(fx_i\) 是单调不降的。假设处理到当前位置时得到了 \(fx_i\) ,发现之前有个更小的数求出LIS的长度 \(\ge i\) ,那么与当前位置LIS长度为 \(i\) 是矛盾的(毕竟当前位置更大,可以接在那个更小的数后面)。所以在之前有个更小的数LIS一定 \(< i\) ,则 \(fx_i\) 是单调不降的。这时就可以处理到第 \(x\) 位时在 \(fx\) 上二分找到小于 \(w\) 的LIS。逆序也要做相同操作。假设两次求得长度为 \(lenl,lenr\) ,答案就是 \(lenl+lenr+1\) 。
假如 \(a_x\) 不再作为LIS中的一个。那就需要判断:\(a_x\) 去除后LIS是否会变短?或者说,在原来的LIS中,\(a_x\) 是否必然为其中的一个?设原序列中LIS长度为 \(len\) 。如果 \(f_x+g_x-1=len\) ,就说明 \(a_x\) 可以出现在原来的LIS上。\(a_x\) 一定出现在LIS的第 \(f_x\) 位。那就要统计一下了,有多少个第 \(f_x\) 位?如果只有一个,说明去除 \(a_x\) 后LIS会变短,答案为 \(len-1\) ;否则答案为 \(len\) 。
将两种可能取最大值即可。
时间复杂度 \(O(N \log N)\) 。
#include<bits/stdc++.h>
using namespace std;
const int N=4e5+5;
const int inf=0x3f3f3f3f;
int n,T;
int a[N];
struct qurs{
int x,val,id;
bool operator<(const qurs& z)const{
return x<z.x;
}
}q[N];
int ans[N];
int f[N],g[N];
int fx[N];
int tong[N];
int qur1(int w){
int l=0,r=n;
while(l<r){
int mid=(l+r+1)>>1;
if(fx[mid]>=w)r=mid-1;
else l=mid;
}
return l;
}
int qur2(int w){
int l=0,r=n;
while(l<r){
int mid=(l+r+1)>>1;
if(fx[mid]<=w)r=mid-1;
else l=mid;
}
return l;
}
int res1[N],res2[N];
int main(){
scanf("%d%d",&n,&T);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
for(int i=1;i<=T;i++){
scanf("%d%d",&q[i].x,&q[i].val);
q[i].id=i;
}
sort(q+1,q+1+T);
int now=0,maxn=0;
for(int i=1;i<=n;i++)fx[i]=inf;
for(int i=1;i<=n;i++){
while(now+1<=T&&q[now+1].x<=i){
now++;
int val=q[now].val,id=q[now].id;
res2[id]+=qur1(val);
}
f[i]=qur1(a[i])+1;
fx[f[i]]=a[i];
maxn=max(maxn,f[i]);
}
now=T+1;
for(int i=1;i<=n;i++)fx[i]=0;
for(int i=n;i>=1;i--){
while(now-1>=1&&q[now-1].x>=i){
now--;
int val=q[now].val,id=q[now].id;
res2[id]+=qur2(val);
}
g[i]=qur2(a[i])+1;
fx[g[i]]=a[i];
if(maxn==f[i]+g[i]-1){
if(tong[f[i]]==0)tong[f[i]]=i;
else tong[f[i]]=-1;
}
}
for(int i=1;i<=n;i++){
if(tong[i]>0)res1[tong[i]]=maxn-1;
}
for(int i=1;i<=n;i++){
if(res1[i]==0)res1[i]=maxn;
}
for(int i=1;i<=T;i++){
int x=q[i].x,id=q[i].id;
ans[id]=max(res1[x],res2[id]+1);
}
for(int i=1;i<=T;i++)printf("%d\n",ans[i]);
return 0;
}
例#2:P2501 [HAOI2006] 数字序列
有一个长度为 \(n\) 的整数序列 \(a\)。
第一行输出一个整数,表示变成一个单调严格上升的序列最少需要改变多少个数。
第二行输出一个整数,表示在改变的数最少的情况下,每个数改变的绝对值之和的最小值。
保证 \(1 \leq n \leq 3.5 \times 10^4\),\(1 \leq a_i \leq 10^5\)。数据保证 \(a_i\) 随机生成。
经典将数组改为LIS最少去掉多少个?
如果是最长不下降子序列,那么答案是 \(n-len\) (\(len\) 是最长长度)。
如果是 LIS,令 \(b_i=a_i-i\) ,此时如果 \(b_i\) 是最长不下降子序列,\(a_i\) 就是 LIS 。
第二问,会把中间不在 LIS 中的数字放入中间。假设 \(j,i\) 是可以连接的,那么枚举分界点 \(k\) ,\(j \sim k\) 都变为 \(b_j\) ,\(k+1 \sim i\) 都变为 \(b_i\) (这样确实是最优的)。dp 计算即可。提前预处理每个 \(i\) 所连接的 \(j\) 可有效降低复杂度。
虽然是暴力枚举,但 \(n\) 比较小,且数据随机,可以通过。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=35005;
int n;
ll a[N];
ll f[N],fx[N];
vector<int>v[N];
ll ans;
ll g[N];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
a[i]+=(n-i);
}
for(int i=1;i<=n;i++)fx[i]=1e5+5;
for(int i=1;i<=n;i++){
int l=0,r=i-1;
while(l<r){
int mid=(l+r+1)>>1;
if(a[i]>=fx[mid])l=mid;
else r=mid-1;
}
f[i]=l+1;
fx[l+1]=a[i];
v[l+1].push_back(i);
ans=max(ans,f[i]);
}
printf("%d\n",n-ans);
v[0].push_back(0);
a[n+1]=1e5+5,f[n+1]=ans+1;
for(int i=1;i<=n+1;i++){
g[i]=1e18;
for(auto j:v[f[i]-1]){
if(j>i||a[j]>a[i])continue;
ll val=0,res=0;
for(int k=j+1;k<=i-1;k++){
val+=abs(a[k]-a[j]);
}
res=val;
for(int k=i-1;k>=j+1;k--){
val-=abs(a[k]-a[j]);
val+=abs(a[k]-a[i]);
res=min(res,val);
}
g[i]=min(g[i],g[j]+res);
}
}
printf("%lld",g[n+1]);
return 0;
}

浙公网安备 33010602011771号