题目: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", n);//易得 124 } 125 return 0; 126 }
如果感觉自己懂了,可以做一下http://acm.hdu.edu.cn/showproblem.php?pid=4453,这也是splay基本题。
一天一道算法题——hdu1890 Robotic Sort(splay树)
原文:https://www.cnblogs.com/zyyz1126/p/12586960.html