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;
}
posted @ 2023-08-29 22:42  Hadtsti  阅读(165)  评论(0编辑  收藏  举报  来源