[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;
}

浙公网安备 33010602011771号