一天一道算法题——hdu1890 Robotic Sort(splay树)

题目:http://acm.hdu.edu.cn/showproblem.php?pid=1890

这题大佬都说是splay树的水题,作为一个新手,苦苦钻研了一天半……我好菜o(╥﹏╥)o

splay树本质上是一棵BST,它会根据需要把最常用的节点旋转到根节点(或者比它更浅的节点),提高效率。所以它是弱平衡树,不需要计算平衡因子和染色,所以维护成本比较低。

关键的旋转代码:

 1 int n, root, pre[maxn], child[maxn][2];
 2 void rotate(int x, int d) {//d为0左旋,1右旋
 3     int y = pre[x];
 4     //以下就是把父节点和旋转方向的孩子交换
 5     child[y][!d] = child[x][d];
 6     pre[child[x][d]] = y;
 7     if (pre[y])//如果父节点有父节点,别忘了把自己换上去
 8         child[pre[y]][child[pre[y]][1] == y] = x;
 9     pre[x] = pre[y];
10     child[x][d] = y;
11     pre[y] = x;
12 }
13 void splay(int r, int goal) {//r是要旋转的节点,goal是要旋转到的地方的父亲
14     while (pre[r] != goal) {
15         if (pre[pre[r]] == goal) {
16             rotate(r, child[pre[r]][0] == r);
17         }
18         else {
19             int y = pre[r];
20             int d = child[pre[y]][0] == y;
21             if (child[y][d] == r)//不共线
22                 rotate(r, !d);
23             else
24                 rotate(y, d);
25             rotate(r, d);
26         }
27     }
28     if (goal == 0)
29         root = r;//更新根节点
30 }

介绍完基本概念,让我们直接进入正题。

题意是要我们找前i次翻转前的第i大的数的位置。如果直接for,时间复杂度O(n^3),n有10^5,肯定会超时。所以就用到了我们的splay算法,他可以方便的将某个节点给splay到根节点,然后它的左孩子的个数+1就是他的结果了。不过据说不删除已经找过的根节点那么就会超时,所以我们要在输出后删除该根节点,同时输出结果应为他的左孩子个数+i。

另外,题目要求我们翻转,但翻转会破坏splay树的有序结构,所以这道题其实不算是严格意义上的splay树,我们只是利用了它的splay而已。ㄟ( ▔, ▔ )ㄏ

以下是AC代码(根据算法竞赛入门进阶——罗勇军著改编):

  1 #include<iostream>
  2 #include<algorithm>
  3 #define maxn 100010
  4 using namespace std;
  5 
  6 int n, root,len[maxn], pre[maxn], child[maxn][2],rev[maxn];//rev是标记——表示是否翻转过,如果每次都翻转那么会超时
  7 struct node {
  8     int val, id;
  9     bool const operator <(node &a)const{
 10         if (val == a.val)return id < a.id;
 11         return val < a.val;
 12     }
 13 }nodes[maxn];
 14 
 15 void pushUp(int x) {//计算长度,不知道为什么要叫pushUp,明明在treap和bst里还叫update的( ・◇・)?
 16     len[x] = len[child[x][0]] + len[child[x][1]]+1;
 17 }
 18 void update_rev(int x) {
 19     if (!x)return;
 20     swap(child[x][0], child[x][1]);
 21     rev[x] ^= 1;
 22 }
 23 void push_down(int x) {
 24     if (rev[x]) {//如果这个节点翻转过,那么把它的孩子节点也需要翻转。
 25         update_rev(child[x][0]);
 26         update_rev(child[x][1]);
 27         rev[x] = 0;
 28     }
 29 }
 30 void rotate(int x, int d) {//d为0左旋,1右旋
 31     int y = pre[x];
 32     push_down(y);
 33     push_down(x);
 34     //以下就是把父节点和旋转方向的孩子交换
 35     child[y][!d] = child[x][d];
 36     pre[child[x][d]] = y;
 37     if (pre[y])//如果父节点有父节点,别忘了把自己换上去
 38         child[pre[y]][child[pre[y]][1] == y] = x;
 39     pre[x] = pre[y];
 40     child[x][d] = y;
 41     pre[y] = x;
 42     pushUp(y);
 43 }
 44 void splay(int r, int goal) {//r是要旋转的节点,goal是要旋转到的地方的父亲
 45     push_down(r);
 46     while (pre[r] != goal) {
 47         if (pre[pre[r]] == goal) {
 48             push_down(pre[r]);
 49             push_down(r);
 50             rotate(r, child[pre[r]][0] == r);
 51         }
 52         else {
 53             push_down(pre[pre[r]]);
 54             push_down(pre[r]);
 55             push_down(r);
 56             int y = pre[r];
 57             int d = child[pre[y]][0] == y;
 58             if (child[y][d] == r)//不共线
 59                 rotate(r, !d);
 60             else
 61                 rotate(y, d);
 62             rotate(r, d);
 63         }
 64     }
 65     pushUp(r);
 66     if (goal == 0)
 67         root = r;
 68 }
 69 void newNode(int &x, int f, int v) {//int &x会直接把传入的x给修改了,这样就不需要重复再给father的左右孩子赋值了
 70     x = v;
 71     len[x] = 1;
 72     pre[x] = f;
 73     rev[x] = 0;
 74     child[x][0] = child[x][1] = 0;
 75 }
 76 void buildTree(int &x, int l, int r, int f) {
 77     //二分保证深度最小
 78     if (l > r)return;
 79     int mid = (l + r) >> 1;
 80     newNode(x,f,mid);
 81     buildTree(child[x][0], l, mid - 1,x);
 82     buildTree(child[x][1], mid + 1, r, x);
 83     pushUp(x);
 84 }
 85 void init() {
 86     root = 0;
 87     child[root][0] = child[root][1] = pre[root] = len[root] = 0;
 88     buildTree(root, 1, n, 0);//以【1,n】为序列建造一棵splaytree
 89 }
 90 void remove(int &x) {
 91     int r = child[x][0];
 92     if (!r) {//没有左孩子
 93         x = child[x][1];
 94         pre[root] = 0;
 95     }
 96     else {
 97         push_down(r);
 98         while(child[r][1]){
 99             r = child[r][1];
100             push_down(r);
101         }
102         splay(r, root);
103         child[r][1] = child[x][1];
104         pre[child[x][1]] = r;
105         x = r;
106         pre[x] = 0;
107         pushUp(x);
108     }
109 }
110 int main() {
111     while (cin >> n && n) {
112         init();
113         for (int i = 1; i <= n; i++) {
114             scanf_s("%d", &nodes[i].val); nodes[i].id = i;//记住位置
115         }
116         sort(nodes + 1, nodes + n + 1);
117         for (int i = 1; i < n; i++) {
118             splay(nodes[i].id, 0);//将第i小的节点splay到根节点(父节点为0)
119             update_rev(child[root][0]);
120             printf("%d ", len[child[root][0]]+i);
121             remove(root);//删除根节点
122         }
123         printf_s("%d
", n);//易得
124     }
125     return 0;
126 }
View Code

如果感觉自己懂了,可以做一下http://acm.hdu.edu.cn/showproblem.php?pid=4453,这也是splay基本题。

原文地址:https://www.cnblogs.com/zyyz1126/p/12586960.html