P4309 [TJOI2013]最长上升子序列(平衡树,FHQ Treap实现)

传送门
一道平衡树好题
因为每次插入的数都是最大的数,所以我们只需要找到插入位置pos的前缀中的LIS,就可以得到此次插入的答案。再把这个答案和全局最大值比较就是此次操作的答案。如何维护一个前缀的LIS就是本题需要实现的操作。
有的题解用了线段树+平衡树维护这个前缀LIS,但其实只使用一个平衡树就够了,具体做法:
考虑按大小分裂的treap,节点中记录一个val,代表该位置结尾的LIS,以及记录一个mx代表该位置前缀的LIS。为了维护前缀的LIS,我尝试过几种错误的方式:

  1. 乱搞一通
  2. 维护的mx只包含了左子树(区间操作的treap和线段树很像但又很不一样)
  3. 只维护了mx,没有维护val

第二和第三的错误让我对treap有了更深的认识,第二种方法的错误是很显然的,平衡树内部的结构不代表左子树就是左区间,要维护左区间需要用其他的方法。
仔细思考一下split的过程,是不断的把子树划分到x树上,这个x树,就是我们需要的前缀区间,那么所有的信息都可以在split的过程中保留下来。在每次split的过程中,我们记录一个mx维护当次分裂的前缀LIS。siz为此次分裂的大小。如果siz > fhq[lson(now)].siz,说明now的整颗左子树都是我们需要的区间。此时可以用fhq[lson(now)].mx和fhq[now].val(注意此时不是mx,具体原因可以自行思考)和下一次的split来更新mx。否则就只用下一次的split更新。这样就能正确的维护出前缀LIS了。

#pragma GCC optimize(2)
#define LL long long
#define pq priority_queue
#define ULL unsigned long long
#define pb push_back
#define mem(a,x) memset(a,x,sizeof a)
#define pii pair<int,int>
#define fir(i,a,b) for(int i=a;i<=(int)b;++i)
#define afir(i,a,b) for(int i=(int)a;i>=b;--i)
#define ft first
#define vi vector<int>
#define sd second
#define ALL(a) a.begin(),a.end()
#define bug puts("-------")
#define mpr(a,b) make_pair(a,b)
#define lson(i) fhq[i].l
#define rson(i) fhq[i].r
#include <bits/stdc++.h>

using namespace std;
const int N = 2e5+10;

inline void read(int &a){
	int x = 0,f=1;char ch = getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch<='9'&&ch>='0'){x=x*10+ch-'0';ch=getchar();}
	a = x*f;
}
struct Node{
	int l,r,siz,key,val,mx;
}fhq[N];
int tot;
std::mt19937 rnd(time(0));
int newnode(int val){
	fhq[++tot].val = val;
	fhq[tot].mx = val;
	fhq[tot].siz = 1;
	fhq[tot].key = rnd();
	return tot;
}
void update(int now){
	fhq[now].siz = fhq[lson(now)].siz + fhq[rson(now)].siz + 1;
	fhq[now].mx = max(fhq[now].val,max(fhq[rson(now)].mx,fhq[lson(now)].mx));
}
int split(int now,int siz,int &x,int &y){
	if(!now){
		x = y = 0;
		return 0;
	}
	int mx = 0;
	if(fhq[lson(now)].siz < siz){
		x = now;
		mx = split(rson(now),siz-fhq[lson(now)].siz-1,rson(now),y);
		mx = max(mx,max(fhq[lson(now)].mx,fhq[now].val));
	}
	else{
		y = now;
		mx = max(mx,split(lson(now),siz,x,lson(now)));
	}
	update(now);
	return mx;
}
int merge(int x,int y){
	if(!x || !y) return x+y;
	if(fhq[x].key >= fhq[y].key){
		rson(x) = merge(rson(x),y);
		update(x);
		return x;
	}
	else{
		lson(y) = merge(x,lson(y));
		update(y);
		return y;
	}
}
int root,x,y,z;
int main(){
	int n;
	cin >> n;
	int ans = 0;
	fir(i,1,n){
		int v;
		cin >> v;
		if(!v)
			root = merge(newnode(1),root),ans = max(ans,1);
		else{
			int mx = split(root,v,x,y);
			ans = max(ans,mx+1);
			root = merge(merge(x,newnode(mx+1)),y);
		}
		cout << ans << endl;
	}
	
	return 0;
}	
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值