CF946G Almost Increasing Array 题解

Solution

弱化版

首先不考虑删数操作,考虑至少修改数组中多少个数才能使其单调递增。

转而考虑未被修改的数必须满足的条件。若最终 \(a_i,a_j(i<j)\) 均未被修改,则有 \(j-i\le a_j-a_i\),即 \(a_i-i\le a_j-j\)。反过来,也能证明任意一个满足该条件的子序列一定能均不被修改。因而问题转化为求 \(n\) 减序列 \(\{a_i-i\}\) 的 LIS 长度。

考虑经典 \(O(n\log n)\) LIS 做法。设 \(f_i\) 为当前长 \(i\) 的不降序列最小末尾(如果不存在则 \(f_i=+\infty\))。不难发现序列 \(f\) 单调不降。每次二分找到最小的 \(k\) 使得 \(f_k\le a_i\) 并令 \(f_{k+1}\leftarrow\min(f_{k+1},a_i)\)。答案即为 \(n-\max(\{k|f_k<+\infty\})\)。

原题

删数操作可以提至所有修改之前。

同理设 \(g_i\) 为当前已删除一个数时,长 \(i\) 的不降序列最小末尾(删的数不算)。\(g\) 同样单调不降。

然后分讨 \(a_i\) 是否删除,共有三个操作。

  • 若 \(a_i\) 删除:对于所有 \(k\),\(g_k\leftarrow \min(g_k,f_k)\)(操作 \(1\))。
  • 若 \(a_i\) 不删除:
    • 用 \(a_i-i\) 更新 \(f\)(操作 \(2\))。
    • 用 \(a_i-i+1\) 更新 \(g\)。因为前面已经删了一个数,所以 \(a_i\) 实际下标为 \(i-1\)。(操作 \(3\))。

为了避免错误的覆盖,需要按 \(3\to 1\to 2\) 的顺序执行三个操作。

然后使用线段树维护 \(f,g\) 即可。最终答案即为 \(n-1-\max(\{k|g_k<+\infty\})\)。

时间复杂度 \(O(n\log n)\)。

Code

cpp 复制代码
#include <bits/stdc++.h>
#define rep(i,a,b) for(int i(a);i<b;++i)
#define rept(i,a,b) for(int i(a);i<=b;++i)
#define ls(p) ((p)<<1)
#define rs(p) ((p)<<1|1)
using namespace std;
constexpr int N=2e5+5,INF=1.1e9;
int n,x,f[N<<2],g[N<<2];
bool tag[N<<2];
void add_tag(int p){
    tag[p]=true;
    g[p]=min(g[p],f[p]);
}
void push_up(int p){
    f[p]=min(f[ls(p)],f[rs(p)]);
    g[p]=min(g[ls(p)],g[rs(p)]);
}
void push_down(int p){
    if(tag[p]){
        add_tag(ls(p));
        add_tag(rs(p));
        tag[p]=0;
    }
}
int get(int tree[],int p,int pl,int pr,int x){  // 查找最后一个<=x的下标
    if(pl==pr) return pl;
    int mid=pl+pr>>1;
    push_down(p);
    if(tree[rs(p)]>x) return get(tree,ls(p),pl,mid,x);
    return get(tree,rs(p),mid+1,pr,x);
}
void upd(int tree[],int p,int pl,int pr,int pos,int x){  // 单点更新
    if(pl==pr){
        tree[p]=min(tree[p],x);
        return;
    }
    int mid=pl+pr>>1;
    push_down(p);
    if(pos<=mid) upd(tree,ls(p),pl,mid,pos,x);
    else upd(tree,rs(p),mid+1,pr,pos,x);
    push_up(p);
}
signed main(){
    cin.tie(0)->sync_with_stdio(0);
    cin>>n>>x;
    fill(f+1,f+(n<<2),INF);
    fill(g+1,g+(n<<2),INF);
    // 以i=1时为初始值
    upd(f,1,0,n,0,-INF);  // f[0]=-INF
    upd(f,1,0,n,1,x-1);  // f[1]=a[1]-1
    upd(g,1,0,n,0,-INF);  // g[0]=-INF
    rept(i,2,n){
        cin>>x;
        upd(g,1,0,n,get(g,1,0,n,x-i+1)+1,x-i+1);  // 操作3
        add_tag(1);  // 操作1
        upd(f,1,0,n,get(f,1,0,n,x-i)+1,x-i);  // 操作2
    }
    cout<<n-1-get(g,1,0,n,INF-1);
    return 0;
}