[Tjoi2013] 最长上升子序列 题解报告

简化题意:

一个空序列,n次操作,第i次在序列的p[i]的位置插入一个点i,问每次操作后最长上升子序列长度

输入 #1

3
0 0 2

输出 #1

1
1
2

说明/提示

\(100\%\) 的数据 \(n\le10^5\)

思路:

求最长上升子序列,想到dp
看看dp的转移条件:转移点的下标<=p[i],转移点的权值<i
从这些点里选dp最大的+1
因为输入就是按权值从小到大输的,所以权值<i不用考虑
思考一下如何维护所有下标<p[i]的位置的dp最大值
看到每次要插入一个点,想到平衡树
让平衡树中的所有点的下标满足二叉搜索树的性质(左子树下标<根下标<右子树下标)
查找就把平衡树中下标<=p[i]的部分分裂出来求dp最大值
然后再把当前点的下标和dp值插入到平衡树中

代码:

#include<bits/stdc++.h>
using namespace std;
#define lson tr[rt].ls
#define rson tr[rt].rs
const int N=1e5+7;
struct FHQ{
    int dp,mxdp,rd,siz,ls,rs;
//dp是当前节点的dp,mxdp是以当前节点为根的子树的最大dp值
}tr[N];
int n,tot,root;
void up(int rt){
    tr[rt].siz=tr[lson].siz+1+tr[rson].siz;
    tr[rt].mxdp=max({tr[rt].dp,tr[lson].mxdp,tr[rson].mxdp});
}
void split(int rt,int v,int &x,int &y){
    if(!rt)return x=0,y=0,void();
    if(tr[lson].siz+1<=v)x=rt,split(rson,v-tr[lson].siz-1,rson,y);
//tr[lson].siz+1就是rt的下标
    else y=rt,split(lson,v,x,lson);
    up(rt);
}
int merge(int ra,int rb){
    if(!ra||!rb)return ra|rb;
    if(tr[ra].rd<tr[rb].rd){
        tr[ra].rs=merge(tr[ra].rs,rb);
        up(ra);return ra;
    }else {
        tr[rb].ls=merge(ra,tr[rb].ls);
        up(rb);return rb;
    }
}
int New(int v){tr[++tot]={v,v,rand(),1};return tot;}
void insert(int pos,int v){
    int x,y;
    split(root,pos,x,y);
    root=merge(merge(x,New(v)),y);
}
int query(int pos){
    int x,y;
    split(root,pos,x,y);
    int mx=tr[x].mxdp;
    root=merge(x,y);
    return mx;
}
int main(){
    ios::sync_with_stdio(0);cin.tie(0);
    cin>>n;
    for(int i=1;i<=n;i++){
        int p;cin>>p;
        int mx=query(p)+1;
        insert(p,mx);
        cout<<tr[root].mxdp<<"\n";
    }
    return 0;
}
posted @ 2026-08-27 19:37  qbc783  阅读(8)  评论(1)    收藏  举报