用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
原创粉丝点击