用Treap实现名次树
来源:互联网 发布:ug4.0钻孔编程教程 编辑:程序博客网 时间:2024/06/06 03:46
来自《算法竞赛入门经典训练指南》
1.Treap实现名次树
1.简单介绍
Treap是一棵拥有键值和优先级两种权值的树。对于键值而言,这棵树是二叉排序树。对于优先级而言,这棵树是堆,即在这棵树的任意子树中,根的优先级是最大的。
不难证明,如果每个结点的优先级事先给定且互不相等,整棵树的形态也就唯一确定了,和元素的插入顺序无关。在Treap的插入算法中,每个节点的优先级是随机确定的。
因此各个操作的时间复杂度也是随机的。幸运的是,可以证明插入、删除和查找的期望时间复杂度均为O(nlogn)。
2.Treap结点的定义
struct Node{ Node *ch[2];//左右子树 int r;//优先级,数值越大,优先级越高 int v;//值 int s;//结点总数 int cmp(int x) const{ if(x==v) return -1; return x<v?0:1; }};
3.旋转
分为左旋和右旋
//d=0表示左旋,d=1表示右旋void rotate(Node* &o,int d){ Node* k=o->ch[d^1]; o->ch[d^1]=k->ch[d]; k->ch[d]=o; o=k;}注意小技巧:因为d=0或者1,d^1定价于1-d,但计算速度更快。关于左旋或者右旋,可自行百度……
4.插入结点
插入结点时,先随机给新结点一个优先级,然后执行普通的插入算法(根据键值大小判断插入哪颗子树中。执行完毕后,用左右旋,维护Treap树的堆性质。
如要采用递归实现,则只需在递归插入之后判断是否需要旋转。
//在以o为根的子树中插入键值x,修改ovoid insert(Node* &o,int x){ if(o==NULL){ o=new Node(); o->ch[0]=o->ch[1]=NULL; o->v=x; o->r=rand(); } else{ int d=o->cmp(x); insert(o->ch[d],x); if(o->ch[d]->r>o->r){ rotate(o,d^1); } }}
5.删除结点
删除结点,如果只有一颗子树,直接用这棵子树代替待删除结点成为根即可(注意o是叶子的情况也符合在内)。麻烦的是o有两棵子树的情况,我们先把优先级较高的一颗旋转到根,然后递归在另一棵子树中删除结点o。例如,左子结点的优先级较高,就必须进行右旋,否则会违反堆性质。
void remove(Node* &o,int x){ int d=o->cmp(x); if(d==-1){ if(o->ch[0]==NULL) o=o->ch[1]; else if(o->ch[1]==NULL) o=o->ch[0]; else{ int d2=(o->ch[0]->r>o->ch[1]->r?1:0); rotate(o,d2); remove(o->ch[d2],x); } } else{ remove(o->ch[d],x); }}
6.查找函数
上面的代码没有处理“待插入值存在”和“待删除值不存在”这两种情况,因此在调用响应函数前,请进行一次查找。
int find(Node* o,int x){ while(o!=NULL){ int d=o->cmp(x); if(d==-1) return 1;//存在 else o=o->ch[d]; } return 0;//不存在}
7.名次树
在利用Treap实现名次树中,每个结点均有一个附加域size,表示以它为根的子树的结点的总数。
名次树可以实现如下两种操作:
Kth(k):找出第k小元素
Rank(x):值x的名次,即比x小的结点个数加1.
用Treap实现名次树的方法是这样的:新增一个size域,然后编写一个maintain函数,方法如下:
void maintain(){ s=1; if(ch[0]!=NULL) s+=ch[0]->s; if(ch[1]!=NULL) s+=ch[1]->s;}旋转操作也要修改,当s可能发生改变时,要调用maintain函数重新计算
//d=0表示左旋,d=1表示右旋void rotate(Node* &o,int d){ Node* k=o->ch[d^1]; o->ch[d^1]=k->ch[d]; k->ch[d]=o; //注意先维护o,再维护k o->maintain(); k->maintain(); o=k;}具体的模板,见下面一道例题。
2.LA5031
1.题目原文
https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=3032
有一张n个顶点m条边的无向图,每个结点有一个整数权值。任务是执行一系列操作,操作分三种。
1.D X:删除ID为X的边,保证每条边至多删一次
2.Q X k:计算与结点X连通的结点中(包括X本身),第k大的权值,如果不存在,返回0.
3.C X V:把结点X的权值改为V。
操作序列结束的标志是E。
2.解题思路
本题只需要设计离线算法,因此可以把操作顺序反过来处理。先读入所有操作,执行所有的操作D时,按照逆序把这些边逐步插入到图中。用一棵名次树维护一个连通分量中的点权。C操作对应修改操作,Q操作对应kth操作,而执行D操作,如果两个端点在一个连通分量中,显然无影响。否则,应该像并查集合并的操作那样,“权值”较小的并到“权值”较大的根节点中,这里的权值是结点数,这样的策略是启发式合并。采用启发式合并,可以高效处理(想一下并查集),对于任意结点来说,每当它被移动到新树中时,该结点所在树的大小至少加倍,任意结点至多“被移动”logn次,每次移动需要O(logn)时间,因此总时间复杂度为O(nlog^2n)。
3.AC代码
#include <algorithm>#include <cctype>#include <cmath>#include <cstdio>#include <cstdlib>#include <cstring>#include <iomanip>#include <iostream>#include <map>#include <queue>#include <string>#include <set>#include <vector>#include<cmath>#include<bitset>#include<sstream>using namespace std;#define INF 0x7fffffffstruct Node{ Node *ch[2];//左右子树 int r;//随机优先级 int v;//值 int s;//结点总数 Node(int v):v(v){ ch[0]=ch[1]=NULL; r=rand(); s=1; } int cmp(int x) const{ if(x==v) return -1; return x<v?0:1; } void maintain(){ s=1; if(ch[0]!=NULL) s+=ch[0]->s; if(ch[1]!=NULL) s+=ch[1]->s; }};//d=0代表左旋,d=1代表右旋void rotate(Node* &o,int d){ Node *k=o->ch[d^1]; o->ch[d^1]=k->ch[d]; k->ch[d]=o; o->maintain(); k->maintain(); o=k;}void insert(Node* &o,int x){ if(o==NULL) o=new Node(x);else{ int d=(x<o->v?0:1);//不要用cmp函数,可能有相同结点 insert(o->ch[d],x); if(o->ch[d]->r>o->r) rotate(o,d^1); } o->maintain();}void remove(Node* &o,int x){ int d=o->cmp(x); if(d==-1){ Node *u=o; if(o->ch[0]!=NULL&&o->ch[1]!=NULL){ int d2=(o->ch[0]->r > o->ch[1]->r?1:0); rotate(o,d2); remove(o->ch[d2],x); } else{ if(o->ch[0]==NULL) o=o->ch[1]; else o=o->ch[0]; delete u; } } else{ remove(o->ch[d],x); } if(o!=NULL) o->maintain();}const int maxc=500000+10;struct Command{ char type; int x,p;//根据type,p代表k或者v}command[maxc];const int maxn=20000+10;const int maxm=60000+10;int n,m;int weight[maxn];int from[maxm],to[maxm],removed[maxm];//并查集相关int pa[maxn];int findset(int x){ return pa[x]!=x?pa[x]=findset(pa[x]):x;}//名次树相关Node *root[maxn];//第k大值int kth(Node* o,int k){ if(o==NULL||k<=0||k>o->s) return 0; int s=(o->ch[1]==NULL?0:o->ch[1]->s); if(k==s+1) return o->v; else if(k<=s) return kth(o->ch[1],k); else return kth(o->ch[0],k-s-1);}void mergeto(Node* &src,Node* &dest){ if(src->ch[0]!=NULL) mergeto(src->ch[0],dest); if(src->ch[1]!=NULL) mergeto(src->ch[1],dest); insert(dest,src->v); delete src; src=NULL;}void removetree(Node* &x){ if(x->ch[0]!=NULL) removetree(x->ch[0]); if(x->ch[1]!=NULL) removetree(x->ch[1]); delete x; x=NULL;}//主程序相关void add_edge(int x){ int u=findset(from[x]),v=findset(to[x]); if(u!=v){ if(root[u]->s<root[v]->s){ pa[u]=v; mergeto(root[u],root[v]); } else{ pa[v]=u; mergeto(root[v],root[u]); } }}int query_cnt;long long query_tot;void query(int x,int k){ query_cnt++; query_tot+=kth(root[findset(x)],k);}void change_weight(int x,int v){ int u=findset(x); remove(root[u],weight[x]); insert(root[u],v); weight[x]=v;}int main(){ int kase=0; while(scanf("%d%d",&n,&m)==2&&n){ for(int i=1;i<=n;i++) scanf("%d",&weight[i]); for(int i=1;i<=m;i++) scanf("%d%d",&from[i],&to[i]); memset(removed,0,sizeof(removed)); //读命令 int c=0; for(;;){ char type; int x,p=0,v=0; scanf(" %c",&type); if(type=='E') break; scanf("%d",&x); if(type=='D') removed[x]=1; if(type=='Q') scanf("%d",&p); if(type=='C'){ scanf("%d",&v); p=weight[x]; weight[x]=v; } command[c++]=(Command){type,x,p}; } //最终的图 for(int i=1;i<=n;i++){ pa[i]=i; if(root[i]!=NULL) removetree(root[i]); root[i]=new Node(weight[i]); } for(int i=1;i<=m;i++){ if(!removed[i]) add_edge(i); } //反向操作 query_cnt=query_tot=0; for(int i=c-1;i>=0;i--){ if(command[i].type=='D') add_edge(command[i].x); if(command[i].type=='C') change_weight(command[i].x,command[i].p); if(command[i].type=='Q') query(command[i].x,command[i].p); } printf("Case %d: %.6lf\n",++kase,query_tot/(double)query_cnt); } return 0;}
0 0
- 用Treap实现名次树
- Treap实现名次树
- 用Treap实现的名次树
- 模板——Treap实现名次树
- Rotate Treap名次树
- BZOJ3224 普通平衡树(Treap实现名次树)
- 【UVALive】5031 Graph and Queries treap实现名次树
- uva1479 - Graph and Queries Treap名次树
- POJ-1442 Black Box,treap名次树!
- 并查集+treap实现名次数(BZOJ2733)
- POJ 2761 Feed the dogs(Treap名次树+离线处理)
- hdu 3726 Graph and Queries (Treap应用,名次树)
- SWUST 2268 SB_cyh and his BST two (Treap名次树)
- 带模板的基于 Treap 的名次树
- UVA 1479 Graph and Queries(Treap:名次树+并查集)
- LA 5031 Graph and Queries (离线处理 + Treap树维护名次)
- poj2985(名次树(treap))找第k大 The k-th Largest Group
- LA 5031 Graph and Queries (【名次树(treap)】+【并查集】+【离线算法】)
- android技术总览
- 开始从事运维工作了
- Extjs4.0日历插件与项目整合的详细介绍(二)
- 2016 年最受欢迎的编程语言是什么?
- 欢迎使用CSDN-markdown编辑器
- 用Treap实现名次树
- ssh学习指南针
- 堆排序(Java实现)
- 使用D3.js实现柱形图的制作
- CSS3快速上手之24:按钮样式说明3
- 小白日记30:kali渗透测试之Web渗透-扫描工具-Skipfish
- Android 圆形进度条
- Fragment与Acticity的信息传递 || Fragment之间的信息传递
- Codeforces 715B