解题思路
题目和最长上升子序列比较像,我们考虑用 d p dp dp来解决
设 f i f_i fi表示以 i i i这个位置结束的最长序列的长度, M a x i Max_i Maxi表示 i i i这个位置最大的数, M i n i Min_i Mini表示 i i i这个位置最小的数,转移时枚举上一个选的元素 j j j是哪个位置
因为最多只会变一个数,所以对于一个转移点 j j j:
·如果是 j j j的位置变化,那么必须满足它变的最大的数也是小于 a i a_i ai的,也就是 M a x j ≤ a i Max_j\leq a_i Maxj≤ai
·如果是
i
i
i的位置变化,那么必须满足它变的最小的数也比
a
j
a_j
aj大,也就是
a
j
≤
M
i
n
i
a_j\leq Min_i
aj≤Mini
也就是我们的转移式是这样的
f
i
=
m
a
x
(
f
j
∣
j
<
i
,
M
a
x
j
≤
a
i
,
a
j
≤
M
i
n
i
)
+
1
f_i=max(f_j|j<i,Max_j \leq a_i,a_j\leq Min_i)+1
fi=max(fj∣j<i,Maxj≤ai,aj≤Mini)+1
这个形式很像三维偏序的形式,可以使用树套树解决,不过我们知道CDQ可以解决一些树套树能够解决的问题,考虑用CDQ分治优化转移
我们回顾
C
D
Q
CDQ
CDQ分治的过程,左半区间的
d
p
dp
dp值可以递归解决,然后我们就知道了左半部分每个位置的
d
p
dp
dp值,并且利用归并可以将左半区间的元素按照
M
a
x
Max
Max值升序排序,然后考虑处理左区间对右区间的转移
我们也可以提前对这些元素排序,满足右半部分的元素按照 a i a_i ai升序排序,然后我们按照 C D Q CDQ CDQ分治的套路,维护双指针 i , j i,j i,j表示对于右半区间的元素 j j j,左半区间 M a x Max Max小于等于 a j a_j aj的最远的一个元素的位置是 i i i,这样我们就解决掉了前两维的限制,之后再用树状数组维护第三维,就能完成转移了
然后再递归计算右半区间内部的转移
void CDQ(int l,int r)
{
if(l==r) //边界条件
{
f[q[l]]=max(f[q[l]],1);
return;
}
int mid=(l+r)>>1,top=l;
for(int i=l;i<=r;i++) if(q[i]<=mid) tmp[top++]=q[i]; //先按照第一维分成左右两部分
for(int i=l;i<=r;i++) if(q[i]>mid) tmp[top++]=q[i];
for(int i=l;i<=r;i++) q[i]=tmp[i];
CDQ(l,mid); //递归计算左边
top=l;
int i=l,j=mid+1;
while(i<=mid&&j<=r)
{
if(Max[q[i]]<=a[q[j]]) add(a[q[i]],f[q[i]]),i++; //在保证第二维的情况下用树状数组维护第三维
else f[q[j]]=max(f[q[j]],ask(Min[q[j]])+1),j++;
}
while(j<=r) f[q[j]]=max(f[q[j]],ask(Min[q[j]])+1),j++;
for(int k=l;k<=mid;k++) cover(a[q[k]],0);//清空树状数组
CDQ(mid+1,r); //递归计算右半部分
i=l;j=mid+1;top=l;
while(i<=mid&&j<=r) //按照第二维归并,方便之后的计算
{
if(Max[q[i]]<=Max[q[j]]) tmp[top++]=q[i++];
else tmp[top++]=q[j++];
}
while(i<=mid) tmp[top++]=q[i++];
while(j<=r) tmp[top++]=q[j++];
for(int k=l;k<=r;k++) q[k]=tmp[k];
}
关于这个转移顺序,不能写成下面这种
void CDQ(int l,int r)
{
CDQ(l,mid);
CDQ(mid+1,r);
calc(l,r)
}
原因是我们进行
d
p
dp
dp的时候需要保证转移点的值是已经被完全计算好的,不会变的
但是如果按照上面的形式,我们左半区间的
d
p
dp
dp值,可能在更高一层才会被转移,所以当前这一层时
d
p
dp
dp值并没有被完全计算好,因此转移时错误的
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+7;
typedef long long LL;
int tree[N];
int f[N];
int a[N];
int n;
int lowbit(int x)
{
return x&(-x);
}
void add(int x,int v)
{
for(int i=x;i<=1e5;i+=lowbit(i))
tree[i]=max(tree[i],v);
}
int ask(int x)
{
int res=0;
for(int i=x;i;i-=lowbit(i))
res=max(res,tree[i]);
return res;
}
void cover(int x,int v)
{
for(int i=x;i<=1e5;i+=lowbit(i))
tree[i]=v;
}
int Min[N],Max[N];
int q[N],tmp[N];
void CDQ(int l,int r)
{
if(l==r)
{
f[q[l]]=max(f[q[l]],1);
return;
}
int mid=(l+r)>>1,top=l;
for(int i=l;i<=r;i++) if(q[i]<=mid) tmp[top++]=q[i];
for(int i=l;i<=r;i++) if(q[i]>mid) tmp[top++]=q[i];
for(int i=l;i<=r;i++) q[i]=tmp[i];
CDQ(l,mid);
top=l;
int i=l,j=mid+1;
while(i<=mid&&j<=r)
{
if(Max[q[i]]<=a[q[j]]) add(a[q[i]],f[q[i]]),i++;
else f[q[j]]=max(f[q[j]],ask(Min[q[j]])+1),j++;
}
while(j<=r) f[q[j]]=max(f[q[j]],ask(Min[q[j]])+1),j++;
for(int k=l;k<=mid;k++) cover(a[q[k]],0);
CDQ(mid+1,r);
i=l;j=mid+1;top=l;
while(i<=mid&&j<=r)
{
if(Max[q[i]]<=Max[q[j]]) tmp[top++]=q[i++];
else tmp[top++]=q[j++];
}
while(i<=mid) tmp[top++]=q[i++];
while(j<=r) tmp[top++]=q[j++];
for(int k=l;k<=r;k++) q[k]=tmp[k];
}
bool cmp(int x,int y)
{
return a[x]<a[y];
}
int m;
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
int x;
scanf("%d",&x);
Min[i]=Max[i]=x;
a[i]=x;
q[i]=i;
}
for(int i=1;i<=m;i++)
{
int x,y;
scanf("%d %d",&x,&y);
Min[x]=min(Min[x],y);
Max[x]=max(Max[x],y);
}
sort(q+1,q+n+1,cmp);
CDQ(1,n);
int ans=1;
for(int i=1;i<=n;i++)
ans=max(ans,f[i]);
cout<<ans;
return 0;
}