解题报告-洛谷P13968 [VKOSHP 2024] Classics

P13968 [VKOSHP 2024] Classics

题目描述

You are probably familiar with the classic problem of finding the longest increasing subsequence in an array. Let \(a\) be an array consisting of \(n\) integers. A subsequence \(i_1 < i_2 < \ldots < i_k\) is called \(\textit{increasing}\) if \(a_{i_1} < a_{i_2} < \ldots < a_{i_k}\). The longest increasing subsequence is the increasing subsequence of maximum length. Of course, we will not ask you to solve the classic problem; you will have to solve its more complicated version...

Initially, there is an empty array \(a\). Then, the numbers \(1, 2, \ldots, n\) are added to the array in this order. The number \(i\) is added to the array at position \(p_i\). Positions in the array are numbered with integers from \(1\) to \(k\), where \(k\) is the current size of the array. When adding an element at position \(p\) in an array of size \(k\), all elements that previously had positions from \(p\) to \(k\) are shifted one position to the right, and the current element is added to the freed space.

Your task is to determine the length of the longest increasing subsequence in the array after each addition of a new element.

输入格式

The first line contains one integer \(n\) (\(1 \le n \le 200\,000\)) --- the number of added elements.

The second line contains \(n\) integers \(p_1, p_2, \ldots, p_n\) (\(1 \le p_i \le i\)) --- \(p_i\) denotes the position where element \(i\) is added.

输出格式

Output \(n\) integers --- the length of the longest increasing subsequence of the array after each addition of a new element.

输入输出样例 #1

输入 #1

5
1 2 1 3 4

输出 #1

1
2
2
2
3

输入输出样例 #2

输入 #2

1
1

输出 #2

1

说明/提示

The array in the first example changed as follows: \([] \to [1] \to [1, 2] \to [3, 1, 2] \to [3, 1, 4, 2] \to [3, 1, 4, 5, 2]\).

解题报告

题目翻译:

你可能熟悉寻找数组中最长递增子序列这一经典问题。设 \(a\) 是一个包含 \(n\) 个整数的数组。子序列 \(i_1 < i_2 < \ldots < i_k\) 被称为递增的,如果 \(a_{i_1} < a_{i_2} < \ldots < a_{i_k}\)。最长递增子序列是长度最大的递增子序列。当然,我们不会要求你解决这个经典问题;你需要解决的是它的更复杂版本...

初始时,有一个空数组 \(a\)。随后,数字 \(1, 2, \ldots, n\) 按顺序被添加到数组中。数字 \(i\) 被添加到数组中的位置 \(p_i\)。数组中的位置从 \(1\)\(k\) 编号,其中 \(k\) 是数组的当前大小。当在大小为 \(k\) 的数组中的位置 \(p\) 添加一个元素时,之前从位置 \(p\)\(k\) 的所有元素向右移动一位,当前元素被添加到腾出的空间中。

你的任务是在每次添加新元素后,确定数组中最长递增子序列的长度。

一道平衡树水题。

题目主要的操作就是将 \(i\) 插入在第 \(p_{i-1}\)\(p_i\) 之间,而这显然可以用按排名分裂的 Treap 来维护。

设当前数组长度为 \(k\),现在考虑将一个数 \(i\) 插入到 \(p_{i-1}\)\(p_i\) 之间后对原数组的影响,有一个很容易注意到的点,就是我们插入的 \(i\) 肯定不会对原数组中每个数的 LIS 造成任何影响。证明很简单,首先区间 \([1,p_{i-1}]\) 的数的 LIS 显然不会收到影响,同时因为这个数 \(i\) 大于原数组中的任意一个数,那么它将不可能作为位于区间 \([p_i,k]\) 的元素的 LIS 前缀。因此,每插入一个数,我们只需计算这个数的最大 LIS 长度,然后把这个值加入平衡树中去维护最大值就好了。

复杂度显然为 \(O(n \log n)\),具体代码如下:

#include<bits/stdc++.h>
using namespace std;
const int INF=0x3f3f3f3f;
const int N=201100;

#define ckmax(x,y) ( x=max(x,y) )
#define ckmin(x,y) ( x=min(x,y) )

inline int read()
{
    int f=1,x=0; char ch=getchar();
    while(!isdigit(ch)) { if(ch=='-') f=-1; ch=getchar(); }
    while(isdigit(ch))  { x=x*10+ch-'0';    ch=getchar(); }
    return f*x;
}

int n;

#define ls(x) T[x].ls
#define rs(x) T[x].rs
struct node
{
    int ls,rs;
    int pri,siz;
    int dp,mdp;
}T[N];
int idx,rt;

inline int NewNode()
{
    int u=++idx;
    ls(u)=rs(u)=0;
    T[u].siz=1;
    T[u].pri=rand();
    T[u].dp=T[u].mdp=1;
    return u;
}

inline void pushup(int u)
{
    if(u==0) return;
    T[u].siz=T[ls(u)].siz+T[rs(u)].siz+1;
    T[u].mdp=max(T[u].dp,max(T[ls(u)].mdp,T[rs(u)].mdp));
}

void Split(int u,int k,int &L,int &R)
{
    if(!u) return (void)( L=R=0 );
    int tmp=T[ls(u)].siz+1;
    if(tmp<=k) {  L=u;Split(rs(u),k-tmp,rs(u),R);  }
    if(tmp>k)  {  R=u;Split(ls(u),k,L,ls(u));      }
    pushup(u);
}

int Merge(int L,int R)
{
    if(!L || !R) return L+R;
    if(T[L].pri>T[R].pri)
    {
        rs(L)=Merge(rs(L),R);
        pushup(L);return L;
    }
    else
    {
        ls(R)=Merge(L,ls(R));
        pushup(R);return R;
    }
}

inline void Insert(int k)
{
    int L,R,p;
    Split(rt,k,L,R);
    p=NewNode();
    T[p].dp=T[L].mdp+1;
    T[p].mdp=T[p].dp;
    rt=Merge(Merge(L,p),R);
}

signed main()
{
    // freopen("P13968.in","r",stdin);
    // freopen("P13968.out","w",stdout);
    srand(time(NULL));
    n=read();
    for(int i=1;i<=n;i++)
    {
        Insert(read()-1);
        printf("%d\n",T[rt].mdp);
    }
    return 0;
}
posted @ 2025-09-07 20:41  南北天球  阅读(14)  评论(0)    收藏  举报