[bzoj3173]最长上升子序列_非旋转Treap

最长上升子序列 bzoj-3173

    题目大意:有1-n,n个数,第i次操作是将i加入到原有序列中制定的位置,后查询当前序列中最长上升子序列长度。

    注释:1<=n<=10,000,开始序列为空。

      想法:显然,我们发现,我每次加入的数一定是当前序列中最大的,所以,刚刚加入的i,要么是当前序列中LIS的结尾,要么不属于LIS。根据这个性质,我们想到:在Treap中维护这样的性质,就是维护每个数加入节点的编号。然后,我们更新新节点的方式就是它的左子树和右子树的LIS取最大+1。其实最重要的就是如何加入这个新的节点?我们引进非旋转Treap。

        非旋转Treap在结构上和Treap一样,都是维护平衡的BST。但是,非旋转Treap在实现上更加暴力和简单。有两个操作,撕裂和合并。

        撕裂?就是去除BST上的一条边,使得原来的非旋转Treap变成两颗树。这样我们就可以对于撕裂开的新子树进行一些单独的处理。例如我们可以撕裂两次,得到一段区间,那么在进行区间反转时就可以仅仅维护一段子树就可以了。

        合并?就是将撕裂之后的子树进行合并即可。这样的话我们对于BST节点的一个权值是随机的,所以我们在合并时还要维护Treap的基本性质。

      这道题的实现和理解无疑是非旋转Treap的裸题,但是非旋转Treap强大的地方已经得到体现。

    最后,附上丑陋的代码... ...

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 300100
#define mp make_pair
using namespace std;
typedef pair<int,int> par;
int n;
int maxx[N],lis[N],size[N],ls[N],rs[N],key[N];
//maxx表示当前节点子树的LIS的长度
//lis表示当前子树中以当前节点结尾的不下降子序列长度
int root;
void update(int x)//这是向上更新
{
	maxx[x]=lis[x];
	size[x]=size[ls[x]]+size[rs[x]]+1;
	maxx[x]=max(maxx[x],max(maxx[ls[x]],maxx[rs[x]]));
}
int lson,rson;
par split(int x,int k)//撕裂操作,返回的是撕裂后两棵新树的根节点,用pair来存储
{
	if(!k) return mp(0,x);
	lson=ls[x],rson=rs[x];par t;
	if(k==size[ls[x]])
	{
		ls[x]=0;update(x);
		return mp(lson,x);
	}
	if(k==size[ls[x]]+1)
	{
		rs[x]=0;update(x);
		return mp(x,rson);
	}
	if(k<size[ls[x]])
	{
		t=split(lson,k);
		ls[x]=t.second;update(x);
		return mp(t.first,x);
	}
	t=split(rson,k-size[ls[x]]-1);
	rs[x]=t.first;update(x);
	return mp(x,t.second);
}
int merge(int x,int y)//合并操作,返回的是合并之后的新树的根节点
{
	if(!x|!y)
	{
		return x|y;
	}
	if(key[x]<key[y])
	{
		ls[y]=merge(x,ls[y]);update(y);
		return y;
	}
	rs[x]=merge(rs[x],y);update(x);
	return x;
}
int main()
{
	scanf("%d",&n);
	int ans=0;
	for(int i=1;i<=n;i++)
	{
		int x;
		scanf("%d",&x);
		size[i]=1;
		key[i]=rand()*rand();
		par t1;
		t1=split(root,x);
		lis[i]=maxx[i]=maxx[t1.first]+1;
		ans=max(ans,lis[i]);
		printf("%d\n",ans);
		root=merge(merge(t1.first,i),t1.second);
	}
	return 0;
}

    小结:非旋转Treap相较于Treap所涉及的范围更广,但虽然代码量有所下降,但是细节更多,难度更大。

      错误:split的!k是要进行特判的,不然会死递归。

原文地址:https://www.cnblogs.com/ShuraK/p/8496087.html

时间: 2024-10-07 18:52:44

[bzoj3173]最长上升子序列_非旋转Treap的相关文章

普通平衡树——非旋转treap

题目: 此为平衡树系列第一道:普通平衡树您需要写一种数据结构,来维护一些数,其中需要提供以下操作:1. 插入x数2. 删除x数(若有多个相同的数,因只删除一个)3. 查询x数的排名(若有多个相同的数,因输出最小的排名)4. 查询排名为x的数5. 求x的前驱(前驱定义为小于x,且最大的数)6. 求x的后继(后继定义为大于x,且最小的数) n<=100000 所有数字均在-107到107内. 输入样例: 10 1 106465 4 1 1 317721 1 460929 1 644985 1 841

非旋转Treap

Treap是一种平衡二叉树,同时也是一个堆.它既具有二叉查找树的性质,也具有堆的性质.在对数据的查找.插入.删除.求第k大等操作上具有期望O(log2n)的复杂度.     Treap可以通过节点的旋转来实现其维持平衡的操作,详见旋转式Treap. 而旋转式Treap在对区间数据的操作上无能为力,这就需要非旋转式Treap来解决这些区间问题. 非旋转式Treap支持的操作 基本操作: 操作 说明 实现复杂度 Build 构造Treap O(n) Merge 合并Treap O(log2n) Sp

4923: [Lydsy1706月赛]K小值查询 平衡树 非旋转Treap

国际惯例的题面:这种维护排序序列,严格大于的进行操作的题都很套路......我们按照[0,k],(k,2k],(2k,inf)分类讨论一下就好.显然第一个区间的不会变化,第二个区间的会被平移进第一个区间,第三个区间的相对大小不会变化.于是我们直接把第二个区间拆了重构,一个一个插入第一个区间即可.因为每次这样做最少减半,所以每个元素只会被重构log次,复杂度nlog^2n.这种按照值域分离区间的操作,非旋转treap实现起来是最简单的......然而第一次写非旋转treap还是出了一点问题,注意它

平衡树讲解(旋转treap,非旋转treap,splay)

在刷了许多道平衡树的题之后,对平衡树有了较为深入的理解,在这里和大家分享一下,希望对大家学习平衡树能有帮助. 平衡树有好多种,比如treap,splay,红黑树,STL中的set.在这里只介绍几种常用的:treap和splay(其中treap包括旋转treap和非旋转treap). 一.treap treap这个词是由tree和heap组合而成,意思是树上的的堆(其实就是字面意思啦qwq).treap可以说是由二叉搜索树(BST)进化而来,二叉搜索树每个点满足它左子树中所有点权值都比它小,它右子

[bzoj1895][Pku3580]supermemo_非旋转Treap

supermemo bzoj-1895 Pku-3580 题目大意:给定一个n个数的序列,需支持:区间加,区间翻转,区间平移,单点插入,单点删除,查询区间最小值. 注释:$1\le n\le 6.1\cdot 10^6$. 想法: 这数据范围给的我真是醉了. 显然用平衡树,这里用非旋转Treap,题目让你干什么你就干什么. 区间加:撕出子树区间后打标记维护区间加. 区间翻转:撕出子树区间后打标记维护区间翻转. 区间平移:相当于两段相邻区间交换,所以撕成四部分:左边,第一个区间,第二个区间,右边.

非旋转Treap详解

利用其他人其中考试的时间,终于学完了非旋转Treap,它与普通Treap的区别就是它不旋转废话.前置知识只有BST和可并堆. BST看这个博客,解释的挺清楚的.https://www.cnblogs.com/jiangminghong/p/9999884.html 可并堆就是用很快的时间合并两个堆.如果裸上一个并查集的话就是nlog2n.这个复杂度我们是不能接受的.正常的可并堆是维护一棵左偏树,我们用一个参数dis[x]表示从x点出发能够向右走的最大步数.每次两个堆合并时,我们就把一个堆扔到另一

bzoj3173 最长上升子序列 树状数组

链接:http://www.lydsy.com/JudgeOnline/problem.php?id=3173 题意:向序列中动态插入$1~n$排列元素,求出插入每个元素后最长上升子序列长度. 如Claris所言,面对这种数据结构,必有高论.如果只想着数据结构,我们可以通过平衡树动态维护序列,同时使用树状数组计算最长上升子序列. 但是我们不是猩猩不是数据结构狂人,我们毕竟还是要本着能不上树就不上树能少用数据结构就少用的原则来设计算法的. 重新考虑这个题目.本题没有要求强制在线,于是我们把整个操作

最长公共子序列_动态规划

具体问题的描绘和分析如下 从上图可知,要构造两个二维数组,数组L用来求各种取值的子最长公共子序列,则最后一个元素就是最长公共子序列的长度,从右边的二维表,数值为1,则就是公共的元素,我们用数组记录下来, 代码如下: 1 #include<bits/stdc++.h> 2 using namespace std; 3 int Maxson(int m,int n,char array[],char array1[],int L[][100],int s[][100],char z[]) 4 {

动态规划_基础_最长公共子序列_多种方法_递归/dp

D: 魔法少女资格面试 题目描述 众所周知,魔法少女是一个低危高薪职业.随着近年来报考魔法少女的孩子们越来越多,魔法少女行业已经出现饱和现象!为了缓和魔法少女界的就业压力,魔法少女考核员丁丁妹决定增加魔法少女资质考核的难度.然而,即使如此,通过资质考核的魔法少女们数量仍然过多,因此,丁丁妹决心增加一轮面试,从而淘汰掉更多的预备魔法少女.具体而言,她打算对所有面试者询问这样一个问题:给两个长度为 n 的全排列,它们的最长公共子序列长度是多少?不幸的是,由于丁丁妹没有好好上过学,她自己也不知道答案是