codeforces 38G - Queue splay伸展树

题目

https://codeforces.com/problemset/problem/38/G

题意:

一些人按顺序进入队列,每个人有两个属性,地位$A$和能力$C$

每个人进入时都在队尾,并最多可以和前一位互换$C$次,如果前一位的地位高于自己,则无法继续互换.

最终一次性输出整个队列

题解:

splay维护动态的队列

可以用类似权值线段树的思维去考虑

对于每个点,保存其子节点的最大值,自身的值,与子树的大小

对于每次插入时,若能跨过整颗右子树与当前节点,则向左走,否则向右

可以保证整个子树中序遍历的顺序即为当前队列

又因为是平衡树,每次插入一个点,都将该点旋转至根,因此均摊复杂度$O(logn)$

#include <bits/stdc++.h>
#define endl ‘\n‘
#define ll long long
#define IO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)
#define rep(ii,a,b) for(int ii=a;ii<=b;++ii)
using namespace std;
const int maxn=1e6+10,maxm=2e6+10;
const int INF=0x3f3f3f3f;
int casn,n,m,k;
class splaytree{
#define nd node[now]
#define ndl	node[node[now].son[0]]
#define ndr node[node[now].son[1]]
	public:
  struct splaynode{
    int son[2],pre;
    int val,mx,size;
    splaynode(){mx=val=size=pre=son[0]=son[1]=0;}
    splaynode(int x,int fa=0){mx=val=x;pre=fa;son[0]=son[1]=0;size=1;}
  };
  int cnt,root;
  vector<splaynode> node;
	void pushup(int now){
		nd.mx=nd.val,nd.size=1;
		rep(i,0,1)if(nd.son[i]) {
			nd.mx=max(node[nd.son[i]].mx,nd.mx);
			nd.size+=node[nd.son[i]].size;
		}
	}
	void pushdown(int now){}
	void rotate(int now,int d){
    int fa=nd.pre;
    pushdown(fa);pushdown(now);
    node[fa].son[!d]=nd.son[d];
    node[nd.son[d]].pre=fa;
    if(node[fa].pre){
			node[node[fa].pre].son[node[node[fa].pre].son[1]==fa]=now;
    }else root=now;
    nd.pre=node[fa].pre;
    nd.son[d]=fa,node[fa].pre=now;
    pushup(fa);pushup(now);
  }
  void splay(int now,int dst){
  	pushdown(now);
    while(nd.pre!=dst){
      if(node[nd.pre].pre==dst)rotate(now,node[nd.pre].son[0]==now);
      else{
	    	int fa=nd.pre,d=(node[node[fa].pre].son[0]==fa);
		    if(node[fa].son[d]==now){rotate(now,!d);rotate(now,d);}
				else {rotate(fa,d);rotate(now,d);}
			}
    }
    if(!dst) root=now;
    pushup(now);
  }
  void insert(int val,int len){
  	if(!root){
			node[++cnt]=splaynode(val);
			root=cnt;
			return;
  	}
  	int now=root;
  	int flag=(val<(max(nd.val,ndr.mx))||len<ndr.size+1);
		while(1){
			if(!nd.son[flag]){
				node[++cnt]=splaynode(val,now);
				nd.son[flag]=cnt;
				break;
			}
			if(!flag)len-=ndr.size+1;
			now=nd.son[flag];
			flag=(val<(max(nd.val,ndr.mx))||len<ndr.size+1);
		}
		pushup(now);
		splay(cnt,0);
  }
  void print(){print(root);}
  void print(int now){
		if(nd.son[0]) print(nd.son[0]);
		cout<<now<<‘ ‘;
		if(nd.son[1]) print(nd.son[1]);
  }
  splaytree(int n){
    node.resize(n+7);
    root=0,cnt=0;
  }
};
int main() {
	IO;
	cin>>n;
	splaytree tree(n);
	rep(i,1,n){
		int a,b;cin>>a>>b;
		tree.insert(a,b);
	}
	tree.print();
	return 0;
}

原文地址:https://www.cnblogs.com/nervendnig/p/10227057.html

时间: 2024-10-29 19:10:27

codeforces 38G - Queue splay伸展树的相关文章

Codeforces 38G Queue 伸展树

题目链接:点击打开链接 题意: 给定n个人来排队 每个人有2个参数,身份优先级和脸皮厚度 == 来的那个人会排到队尾 如果这个人的优先级比他前面那个人的优先级大就会和前面那个人交换位置. 交换一次脸皮厚度减1, 一直交换到队头或者脸皮厚度为0 交换完成后下一个人才会到来. 问: 队伍最后的情况(从队头到队尾依次输出每个人的编号) 思路:splay 维护子树的最小值. 插入时递归插入,若当前点是空就插这个位置. 然后就是裸的splay.. == #include <stdio.h> #inclu

bzoj1208 splay伸展树

splay伸展树主要有两种操作形式 (1)正常的二叉树插入形式 功能:a.查找 b.求最大值 c.最小值 d.求前驱 e.求后继 f.删点 g.合并splay树 (这里的删除直接利用splay树的结点下标) (2)区间形式 (插入是以区间形式插入的) 区间形式的伸展树相当于线段树,支持线段树的所有操作,并且还支持区间插入这个功能, 比如操作区间[a,b],将根设为a-1,根的右孩子设为b+1,那么根的右孩子的左孩子就是所求区间 某个点插入区间也是一个道理 需要注意的是,这里init()自动生成了

Splay伸展树学习笔记

Splay伸展树 有篇Splay入门必看文章 —— CSDN链接 经典引文 空间效率:O(n) 时间效率:O(log n)插入.查找.删除 创造者:Daniel Sleator 和 Robert Tarjan 优点:每次查询会调整树的结构,使被查询频率高的条目更靠近树根. Tree Rotation 树的旋转是splay的基础,对于二叉查找树来说,树的旋转不破坏查找树的结构. Splaying Splaying是Splay Tree中的基本操作,为了让被查询的条目更接近树根,Splay Tree

【学时总结】◆学时&#183;VI◆ SPLAY伸展树

◆学时·VI◆ SPLAY伸展树 平衡树之多,学之不尽也-- ◇算法概述 二叉排序树的一种,自动平衡,由 Tarjan 提出并实现.得名于特有的 Splay 操作. Splay操作:将节点u通过单旋.双旋移动到某一个指定位置. 主要目的是将访问频率高的节点在不改变原顺序的前提下移动到尽量靠近根节点的位置,以此来解决同一个(相似)问题的多次查询. 但是在非降序查询每一个节点后,Splay 树会变为一条链,降低运算效率. ◇原理&细解 (1)旋转操作 二叉排序树必须满足 左儿子<根节点<右

Splay伸展树

伸展树,感觉对我这种菜鸟还是有些吃力,主要也是旋转的时候吧,把要查询的节点旋转到根节点,看网上是有两种方法,一是从上到下,一是从下到上.从上到下,是把树拆分成中树,左树和右树,始终保证目标节点在中树,不断向下把中树的节点添到右树或者左树,直到目标节点为中树的根,再将三树合并起来.另外一种从下到上,旋转操作类似AVL树中的旋转,但是判断好像不是很好写,我写的是从上到下的,另外删除也很巧妙,先把目标节点旋转到根,若此时左子树为空直接删除,否则找到左子树最右的节点当头,利用伸展树的特殊旋转就可以一步删

UVA 11922 Permutation Transformer —— splay伸展树

题意:根据m条指令改变排列1 2 3 4 - n ,每条指令(a, b)表示取出第a~b个元素,反转后添加到排列尾部 分析:用一个可分裂合并的序列来表示整个序列,截取一段可以用两次分裂一次合并实现,粘贴到末尾可以用一次合并实现. 翻转可以采用在每个结点上做标记的方法,flip = 1意味着将这棵子树翻转,可以类似线段树用一个pushdown()实现标记向下传递. 可以发现当前排列就是伸展树的中序遍历序列.中序遍历打印结果即可. 注意代码中设置了虚拟首结点0的技巧. 代码如下: 1 #includ

Splay伸展树入门(单点操作,区间维护)

ps:终于学会了伸展树的区间操作,做一个完整的总结,总结一下自己的伸展树的单点操作和区间维护,顺便给未来的总结复习用. splay是一种平衡树,[平均]操作复杂度O(nlogn).首先平衡树先是一颗二叉搜索树,刚刚开始学的时候找题hash数字的题先测板子... 后来那题被学长改了数据不能用平衡树测了...一道二分数字的题. 二叉搜索树的功能是,插入一个数字,在O(logn)的时间内找到它,并操作,插入删除等.但是可能会让二叉搜索树退化成链,复杂度达到O(n) 原文地址:https://www.c

CodeForces 91B Queue (线段树单点操作)

Description There are n walruses standing in a queue in an airport. They are numbered starting from the queue's tail: the 1-st walrus stands at the end of the queue and the n-th walrus stands at the beginning of the queue. The i-th walrus has the age

splay伸展树模板

1 struct SplayTree 2 { 3 4 const static int maxn = 1e5 + 15; 5 6 int tot,root,ch[maxn][2], key[maxn], sz[maxn], lz[maxn], fa[maxn]; 7 8 void init( int x, int v = 0, int par = 0 ) 9 { 10 ch[x][0]=ch[x][1]=0, fa[x]= par, key[x] = v, sz[x] = 1; 11 } 12