CF474E 题解
题意简述
给出长度为 $n(1\le n\le 10^5)$ 的序列 $a_i(1\le a_i\le 10^{15})$,求 $a_i$ 的最长的一个子序列 $\{b_1,b_2,\cdots,b_m\}$,满足 $\forall i\in[1,m),|b_i-b_{i+1}|\ge d$($d$ 为给定数且 $d\le 10^9$)。
题目分析
裸的 DP 是好想的。类似于经典的 LIS 问题,设 $f_i$ 为 $a_i,a_{i+1},\cdots,a_n$ 满足题意的子序列的最长长度。那么我们只需要倒着求 $f_i$,对每个 $f_i$ 有 $\displaystyle f_i=\max_{j=i+1,|a_i-a_j|\ge d}^n\{f_j+1\}$,直接求是 $O(n^2)$ 的。至于具体数列,只需要对每个 $i$ 记录更新它的 $j$ 即可。
考虑优化。注意到 $|a_i-a_j|\ge d\Leftrightarrow a_j\in [1,a_i-d]\cup[a_i+d,10^{15}]$。那么我们只需要找到满足 $j\ge i$ 且 $a_j$ 在上述区间中的最大的 $f_j$ 即可。考虑离散化后使用线段树维护,仍是倒着求 $f_i$。对于 $f_i$,先区间查询线段树中值域在 $[1,a_i-d]\cup[a_i+d,10^{15}]$ 的最大 $f$ 值,然后单点修改 $a_i$ 对应的 $f$ 值为 $f_i$。注意维护时顺便记录一下区间里最大的 $f_j$ 对应的 $j$。总时间复杂度 $O(n\log n)$。具体细节见代码。
代码实现
#include<bits/stdc++.h>
using namespace std;
int n,m,nt[100010],mx=1,mxid=1;
long long d,a[100010],b[100010];
struct node
{
int mx,mxid;//mx:区间最大 f 值,mxid:区间最大 f 值对应的 j
}tr[400010];//线段树结点
node calc(node a,node b)//合并两个结点
{
node c;
if(a.mx>b.mx)
{
c.mx=a.mx;
c.mxid=a.mxid;
}
else
{
c.mx=b.mx;
c.mxid=b.mxid;
}
return c;
}
void pushup(int p)//子结点更新父节点
{
tr[p]=calc(tr[p<<1],tr[p<<1|1]);
}
void build(int p,int l,int r)//建树
{
if(l==r)
{
tr[p].mx=-1;//初始 f 全设为 -1。
tr[p].mxid=0;
return;
}
int mid=l+r>>1;
build(p<<1,l,mid);
build(p<<1|1,mid+1,r);
pushup(p);
}
void change(int p,int x,int v,int id,int l,int r)//单点修改
{
if(l==r)
{
if(v>tr[p].mx)//注意 a_i 可能有重复值,因此是取 max 不是直接赋值
{
tr[p].mx=v;
tr[p].mxid=id;
}
return;
}
int mid=l+r>>1;
if(mid>=x)
change(p<<1,x,v,id,l,mid);//改左子结点
else
change(p<<1|1,x,v,id,mid+1,r);//改右子结点
pushup(p);
}
node query(int p,int L,int R,int l,int r)//区间查询
{
if(r<L||l>R)
return (node){-1,0}; //区间无交集返回无解
if(l>=L&&r<=R)
return tr[p];//区间全包含就直接返回答案
int mid=l+r>>1;
if(mid<L)
return query(p<<1|1,L,R,mid+1,r);//只查询左子结点
else if(mid>=R)
return query(p<<1,L,R,l,mid);//只查询右子结点
else
return calc(query(p<<1,L,R,l,mid),query(p<<1|1,L,R,mid+1,r));//合并子结点的查询答案
}
int main()
{
scanf("%d%lld",&n,&d);
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]),b[i]=a[i];
sort(b+1,b+n+1);
m=unique(b+1,b+n+1)-b-1;//离散化
build(1,1,m);
for(int i=n;i>=1;i--)
{
node ans=calc(query(1,1,upper_bound(b+1,b+m+1,a[i]-d)-b-1,1,m),query(1,lower_bound(b+1,b+m+1,a[i]+d)-b,m,1,m)); //查询 [1,a_i-d]∪[a_i+d,+∞] 的最大 f
if(ans.mx!=-1)//如果有 f 就从那个 f 更新
{
if(ans.mx>=mx)
mx=ans.mx+1,mxid=i;//更新答案
change(1,lower_bound(b+1,b+m+1,a[i])-b,ans.mx+1,i,1,m);//单点修改
nt[i]=ans.mxid;//记录从哪个 f 过来的
}
else//否则答案为 1
change(1,lower_bound(b+1,b+m+1,a[i])-b,1,i,1,m);//直接修改
}
printf("%d\n",mx);//更新答案
while(mxid)//输出序列
printf("%d ",mxid),mxid=nt[mxid];
return 0;
}