【模板】Treap

借鉴   模板    讲解

模板题

 

看了一上午才看明白,Treap = Tree + Heap,是棵弱平衡树。

一棵树同时具有二叉搜索树的性质和堆的性质,可以完成二叉搜索树和堆的操作。

 

有人曰:

Treap代码量小,速度较快

不能分裂合并,可以可持久化

挺好的东西。

于是我就学了。

 

可完成的操作:1.插入x

       2.删除x数(若有多个相同的数,因只删除一个)

       3.查询x数的排名(若有多个相同的数,因输出最小的排名)

       4.查询排名为x的数

       5.求x的前驱(前驱定义为小于x,且最大的数)

       6.求x的后继(后继定义为大于x,且最小的数)

 

说明:

son[k][0] 表示 k 的左儿子, son[k][1] 表示 k 的右儿子
size[k] 表示以 k 为根的子树的节点数
w[k] 表示节点 k 的权值
rnd[k] 表示节点 k 的优先级
g[k] 表示节点 k 的个数(有可能有重复的)

 

0.旋转

最关键的操作,运用了二进制优化,使左旋和右旋代码合并。

给张左旋右旋的图,可以通过这张图来推出左旋右旋代码。(来自百度百科)

 1 //通过旋转来维护小根堆 
 2 //因为通过旋转根是会变的,所以得加 &  
 3 // x 为 0 是 右旋,x 为 1 是 左旋
 4 //但是不必知道是左旋还是右旋
 5 //只知道旋转一个节点的孩子就相当于把这个孩子旋转到父亲上 
 6 void turn(int &k, int x)
 7 {
 8     //位运算优化 ^ 可表示兄弟节点, 使得左旋和右旋合并 
 9     int t = son[k][x];
10     son[k][x] = son[t][x ^ 1];
11     son[t][x ^ 1] = k;
12     size[t] = size[k];
13     size[k] = size[son[k][0]] + size[son[k][1]] + g[k];
14     k = t;
15 }
turn

1.插入x

可以看出,每次插入都是插入叶节点,所以直接递归处理就好。

同时注意更新size数组,通过旋转保持堆的性质。

 1 void insert(int &k, int x)//小根堆_插入 
 2 {
 3     if(!k)
 4     {
 5         k = ++tot;
 6         w[k] = x;
 7         rnd[k] = rand();
 8         g[k] = size[k] = 1;
 9         return;
10     }
11     size[k]++;
12     if(w[k] == x) g[k]++;
13     else if(w[k] < x)
14     {
15         insert(son[k][1], x);
16         if(rnd[son[k][1]] < rnd[k]) turn(k, 1);
17     }
18     else
19     {
20         insert(son[k][0], x);
21         if(rnd[son[k][0] < rnd[k]]) turn(k, 0);
22     }
23 } 
insert

2.删除x

1.如果是叶子节点就直接删除

2.如果只有一个儿子,就子继父位

3.如果有两个儿子,就通过旋转,把较小儿子翻转到上面,把当前节点翻转到下面,知道当前节点为情况1或2

 1 //通过旋转使得被删除点转移得到叶子节点上或只有一个儿子 
 2 void del(int &k, int x)//删除 
 3 {
 4     if(!k) return;
 5     if(w[k] == x)
 6     {
 7         if(g[k] > 1)
 8         {
 9             g[k]--;
10             size[k]--;
11             return;
12         }
13         //只有一个儿子, 直接继承
14         if(son[k][0] * son[k][1] == 0) k = son[k][0] + son[k][1];
15         //旋转后须满足小根堆性质,所以右旋,旋转后 k 值改变,继续删除 
16         else if(rnd[son[k][0]] < rnd[son[k][1]]) turn(k, 0), del(k, x);
17         //反之左旋 
18         else turn(k, 1), del(k, x);
19     }
20     else
21     {
22         size[k]--;
23         if(w[k] < x) del(son[k][1], x);
24         else del(son[k][0], x);
25     }
26 }
delete

3.查询x的排名

递归处理,具体细节看代码。

1 //查询x数的排名(若有多个相同的数,因输出最小的排名)
2 int get_rank(int k, int x)
3 {
4     if(!k) return 0;
5     if(w[k] == x) return size[son[k][0]] + 1;
6     else if(w[k] < x) return size[son[k][0]] + g[k] + get_rank(son[k][1], x);
7     else return get_rank(son[k][0], x);
8 }
find_rank

4.查询排名为x的数

递归处理,具体细节看代码。

1 //找第 x 大的数 
2 int get_kth(int k, int x)
3 {
4     if(!k) return 0;
5     if(size[son[k][0]] >= x) return get_kth(son[k][0], x);
6     else if(size[son[k][0]] + g[k] < x) return get_kth(son[k][1], x - size[son[k][0]] - g[k]);
7     else return w[k];
8 }
find_Kth

5.求x的前驱

递归处理,具体细节看代码。

1 // x 的前驱是比 x 小的数中最大的那个
2 void get_pre(int k, int x)
3 {
4     if(!k) return;
5     if(w[k] >= x) get_pre(son[k][0], x);
6     else ans = k, get_pre(son[k][1], x);
7 }
find_pre

6.求x的后继

递归处理,具体细节看代码。

1 // x 的后继是比 x 大的数中最小的那个
2 void get_suc(int k, int x)
3 {
4     if(!k) return;
5     if(w[k] <= x) get_suc(son[k][1], x);
6     else ans = k, get_suc(son[k][0], x);
7 }
find_suc

最后是凑字数的主程序

 1 int main()
 2 {
 3     int i, opt, x;
 4     scanf("%d", &n);
 5     for(i = 1; i <= n; i++)
 6     {
 7         scanf("%d %d", &opt, &x);
 8         switch(opt)
 9         {
10             case 1: insert(root, x); break;
11             case 2: del(root, x); break;
12             case 3: printf("%d\n", get_rank(root, x)); break;
13             case 4: printf("%d\n", get_kth(root, x)); break;
14             case 5: ans = 0; get_pre(root, x); printf("%d\n", w[ans]); break;
15             case 6: ans = 0; get_suc(root, x); printf("%d\n", w[ans]); break;
16         }
17     }
18     return 0;
19 }
main

 

完整代码

  1 #include <cstdio>
  2 #include <cstdlib>
  3 
  4 using namespace std;
  5 
  6 int n, root, ans, tot;
  7 int son[100001][2], size[100001], w[100001], rnd[100001], g[100001];
  8 //son[k][0] 表示 k 的左儿子, son[k][1] 表示 k 的右儿子
  9 //size[k] 表示以 k 为根的子树的节点数
 10 //w[k] 表示节点 k 的权值
 11 //rnd[k] 表示节点 k 的优先级
 12 //g[k] 表示节点 k 的个数(有可能有重复的) 
 13 
 14 //通过旋转来维护小根堆 
 15 //因为通过旋转根是会变的,所以得加 &  
 16 // x 为 0 是 右旋,x 为 1 是 左旋
 17 //但是不必知道是左旋还是右旋
 18 //只知道旋转一个节点的孩子就相当于把这个孩子旋转到父亲上 
 19 void turn(int &k, int x)
 20 {
 21     //位运算优化 ^ 可表示兄弟节点, 使得左旋和右旋合并 
 22     int t = son[k][x];
 23     son[k][x] = son[t][x ^ 1];
 24     son[t][x ^ 1] = k;
 25     size[t] = size[k];
 26     size[k] = size[son[k][0]] + size[son[k][1]] + g[k];
 27     k = t;
 28 }
 29 
 30 void insert(int &k, int x)//小根堆_插入 
 31 {
 32     if(!k)
 33     {
 34         k = ++tot;
 35         w[k] = x;
 36         rnd[k] = rand();
 37         g[k] = size[k] = 1;
 38         return;
 39     }
 40     size[k]++;
 41     if(w[k] == x) g[k]++;
 42     else if(w[k] < x)
 43     {
 44         insert(son[k][1], x);
 45         if(rnd[son[k][1]] < rnd[k]) turn(k, 1);
 46     }
 47     else
 48     {
 49         insert(son[k][0], x);
 50         if(rnd[son[k][0] < rnd[k]]) turn(k, 0);
 51     }
 52 } 
 53 
 54 //通过旋转使得被删除点转移得到叶子节点上或只有一个儿子 
 55 void del(int &k, int x)//删除 
 56 {
 57     if(!k) return;
 58     if(w[k] == x)
 59     {
 60         if(g[k] > 1)
 61         {
 62             g[k]--;
 63             size[k]--;
 64             return;
 65         }
 66         //只有一个儿子, 直接继承
 67         if(son[k][0] * son[k][1] == 0) k = son[k][0] + son[k][1];
 68         //旋转后须满足小根堆性质,所以右旋,旋转后 k 值改变,继续删除 
 69         else if(rnd[son[k][0]] < rnd[son[k][1]]) turn(k, 0), del(k, x);
 70         //反之左旋 
 71         else turn(k, 1), del(k, x);
 72     }
 73     else
 74     {
 75         size[k]--;
 76         if(w[k] < x) del(son[k][1], x);
 77         else del(son[k][0], x);
 78     }
 79 }
 80 
 81 //查询x数的排名(若有多个相同的数,因输出最小的排名)
 82 int get_rank(int k, int x)
 83 {
 84     if(!k) return 0;
 85     if(w[k] == x) return size[son[k][0]] + 1;
 86     else if(w[k] < x) return size[son[k][0]] + g[k] + get_rank(son[k][1], x);
 87     else return get_rank(son[k][0], x);
 88 }
 89 
 90 //找第 x 大的数 
 91 int get_kth(int k, int x)
 92 {
 93     if(!k) return 0;
 94     if(size[son[k][0]] >= x) return get_kth(son[k][0], x);
 95     else if(size[son[k][0]] + g[k] < x) return get_kth(son[k][1], x - size[son[k][0]] - g[k]);
 96     else return w[k];
 97 }
 98 
 99 // x 的前驱是比 x 小的数中最大的那个
100 void get_pre(int k, int x)
101 {
102     if(!k) return;
103     if(w[k] >= x) get_pre(son[k][0], x);
104     else ans = k, get_pre(son[k][1], x);
105 }
106 
107 // x 的后继是比 x 大的数中最小的那个
108 void get_suc(int k, int x)
109 {
110     if(!k) return;
111     if(w[k] <= x) get_suc(son[k][1], x);
112     else ans = k, get_suc(son[k][0], x);
113 }
114 
115 int main()
116 {
117     int i, opt, x;
118     scanf("%d", &n);
119     for(i = 1; i <= n; i++)
120     {
121         scanf("%d %d", &opt, &x);
122         switch(opt)
123         {
124             case 1: insert(root, x); break;
125             case 2: del(root, x); break;
126             case 3: printf("%d\n", get_rank(root, x)); break;
127             case 4: printf("%d\n", get_kth(root, x)); break;
128             case 5: ans = 0; get_pre(root, x); printf("%d\n", w[ans]); break;
129             case 6: ans = 0; get_suc(root, x); printf("%d\n", w[ans]); break;
130         }
131     }
132     return 0;
133 }
View Code

 

posted @ 2017-04-18 11:45  zht467  阅读(158)  评论(0)    收藏  举报