[HNOI 2017]单旋

Description

H 国是一个热爱写代码的国家,那里的人们很小去学校学习写各种各样的数据结构。伸展树(splay)是一种数据

结构,因为代码好写,功能多,效率高,掌握这种数据结构成为了 H 国的必修技能。有一天,邪恶的“卡”带着

他的邪恶的“常数”来企图毁灭 H 国。“卡”给 H 国的人洗脑说,splay 如果写成单旋的,将会更快。“卡”称

“单旋 splay”为“spaly”。虽说他说的很没道理,但还是有 H 国的人相信了,小 H 就是其中之一,spaly 马

上成为他的信仰。 而 H 国的国王,自然不允许这样的风气蔓延,国王构造了一组数据,数据由 m 个操作构成,

他知道这样的数据肯定打垮 spaly,但是国王还有很多很多其他的事情要做,所以统计每个操作所需要的实际代价

的任务就交给你啦。

数据中的操作分为五种:

1. 插入操作:向当前非空 spaly 中插入一个关键码为 key 的新孤立节点。插入方法为,先让 key 和根比较,如果

key 比根小,则往左子树走,否则往右子树走,如此反复,直到某个时刻,key 比当前子树根 x 小,而 x 的左子

树为空,那就让 key 成为 x 的左孩子; 或者 key 比当前子树根 x 大,而 x 的右子树为空,那就让 key 成为

x 的右孩子。该操作的代价为:插入后,key 的深度。特别地,若树为空,则直接让新节点成为一个单个节点的树

。(各节点关键码互不相等。对于“深度”的解释见末尾对 spaly 的描述)。

2. 单旋最小值:将 spaly 中关键码最小的元素 xmin 单旋到根。操作代价为:单旋前 xmin 的深度。

(对于单旋操作的解释见末尾对 spaly 的描述)。

3. 单旋最大值:将 spaly 中关键码最大的元素 xmax 单旋到根。操作代价为:单旋前 xmax 的深度。

4. 单旋删除最小值:先执行 2 号操作,然后把根删除。由于 2 号操作之后,根没有左子树,所以直接切断根和右子

树的联系即可(具体见样例解释)。 操作代价同 2 号操 作。

5. 单旋删除最大值:先执行 3 号操作,然后把根删除。 操作代价同 3 号操作。

对于不是 H 国的人,你可能需要了解一些 spaly 的知识,才能完成国王的任务:

a. spaly 是一棵二叉树,满足对于任意一个节点 x,它如果有左孩子 lx,那么 lx 的关键码小于 x 的关键码。

如果有右孩子 rx,那么 rx 的关键码大于 x 的关键码。

b. 一个节点在 spaly 的深度定义为:从根节点到该节点的路径上一共有多少个节点(包括自己)。

c. 单旋操作是对于一棵树上的节点 x 来说的。一开始,设 f 为 x 在树上的父亲。如果 x 为 f 的左孩子,那么

执行 zig(x) 操作(如上图中,左边的树经过 zig(x) 变为了右边的树),否则执行 zag(x) 操作(在上图中,将

右边的树经过 zag(f) 就变成了左边的树)。每当执 行一次 zig(x) 或者 zag(x),x 的深度减小 1,如此反复,

直到 x 为根。总之,单旋 x 就是通过反复执行 zig 和 zag 将 x 变为根。

Input

第一行单独一个正整数 m。

接下来 m 行,每行描述一个操作:首先是一个操作编号 c∈[1,5],即问题描述中给出的五种操作中的编号,若 c

= 1,则再输入一个非负整数 key,表示新插入节点的关键码。

1≤m≤10^5,1≤key≤10^9

所有出现的关键码互不相同。任何一个非插入操作,一定保证树非空。在未执行任何操作之前,树为空

Output

输出共 m 行,每行一个整数,第 i 行对应第 i 个输入的操作的代价。

Sample Input

5
1 2
1 1
1 3
4
5

Sample Output

1
2
2
2
2

题解

参考了PIPIBoss的做法。

我们手玩一下单旋,发现其如果只改变最大最小值时,主要的性质就是整棵$splay$的形态不会发生很大的改变。

例如旋最小值到根,其实就相当于将最小值的节点取出来,最小值的右儿子连向最小值的父亲。接着再把最小值对应的节点接在原来的根的父亲上。

最大值同理。那么就可以用$LCT$维护了。

对于插入操作,很显然的是这个新插入的节点肯定接向其前驱的右儿子或后继的左儿子。另外,这两个儿子肯定有一个是空的,有一个不空。另外,不空的儿子就是前驱和后继两个中深度较深节点的儿子。

我们将权值离散,找前驱后继就交给$STL-set$,另外还要额外开数组来记录原来$splay$树的形态。

  1 //It is made by Awson on 2017.12.27
  2 #include <map>
  3 #include <set>
  4 #include <cmath>
  5 #include <ctime>
  6 #include <queue>
  7 #include <stack>
  8 #include <vector>
  9 #include <cstdio>
 10 #include <string>
 11 #include <cstdlib>
 12 #include <cstring>
 13 #include <iostream>
 14 #include <algorithm>
 15 #define LL long long
 16 #define LD long double
 17 #define Max(a, b) ((a) > (b) ? (a) : (b))
 18 #define Min(a, b) ((a) < (b) ? (a) : (b))
 19 using namespace std;
 20 const int N = 1e5;
 21 const int INF = ~0u>>1;
 22
 23 int m, a[N+5], top;
 24 struct Opt {
 25     int opt, x;
 26 }q[N+5];
 27 struct Link_Cut_Tree {
 28     int ch[N+5][2], pre[N+5], size[N+5], isrt[N+5], rev[N+5];
 29     set<int>S;
 30     int f[N+5], c[N+5][2], root;
 31     Link_Cut_Tree () {
 32     for (int i = 1; i <= N; i++) size[i] = isrt[i] = 1;
 33     S.insert(-INF), S.insert(INF);
 34     }
 35     void pushdown(int o) {
 36     if (!o || !rev[o]) return;
 37     int ls = ch[o][0], rs = ch[o][1];
 38     swap(ch[ls][0], ch[ls][1]), swap(ch[rs][0], ch[rs][1]);
 39     rev[ls] ^= 1, rev[rs] ^= 1, rev[o] = 0;
 40     }
 41     void push(int o) {
 42     if (!isrt[o]) push(pre[o]);
 43     pushdown(o);
 44     }
 45     void pushup(int o) {
 46     if (!o) return;
 47     size[o] = size[ch[o][0]]+size[ch[o][1]]+1;
 48     }
 49     void rotate(int o, int kind) {
 50     int p = pre[o];
 51     ch[p][!kind] = ch[o][kind], pre[ch[o][kind]] = p;
 52     if (isrt[p]) isrt[o] = 1, isrt[p] = 0;
 53     else ch[pre[p]][ch[pre[p]][1] == p] = o;
 54     pre[o] = pre[p];
 55     ch[o][kind] = p, pre[p] = o;
 56     pushup(p), pushup(o);
 57     }
 58     void splay(int o) {
 59     push(o);
 60     while (!isrt[o]) {
 61         if (isrt[pre[o]]) rotate(o, ch[pre[o]][0] == o);
 62         else {
 63         int p = pre[o], kind = ch[pre[p]][0] == p;
 64         if (ch[p][kind] == o) rotate(o, !kind), rotate(o, kind);
 65         else rotate(p, kind), rotate(o, kind);
 66         }
 67     }
 68     }
 69     void access(int o) {
 70     int y = 0;
 71     while (o) {
 72         splay(o); size[o] -= size[ch[o][1]];
 73         isrt[ch[o][1]] = 1, isrt[ch[o][1] = y] = 0;
 74         o = pre[y = o];
 75         pushup(o);
 76     }
 77     }
 78     void makeroot(int o) {
 79     access(o), splay(o);
 80     rev[o] ^= 1, swap(ch[o][0], ch[o][1]);
 81     }
 82     void link(int x, int y) {
 83     if (!x || !y) return;
 84     makeroot(x); pre[x] = y;
 85     }
 86     void cut(int x, int y) {
 87     if (!x || !y) return;
 88     makeroot(x), access(y), splay(y);
 89     size[y] -= size[x];
 90     ch[y][0] = pre[x] = 0, isrt[x] = 1;
 91     }
 92     int query(int x, int y) {
 93     makeroot(x), access(y), splay(y);
 94     return size[ch[y][0]]+1;
 95     }
 96     int insert(int x) {
 97     if (!root) {
 98         root = x; S.insert(x); return 1;
 99     }
100     int pre = *(--S.lower_bound(x)), nex = *(S.upper_bound(x)), o;
101     if (pre == -INF) o = nex;
102     else if (nex == INF) o = pre;
103     else {
104         int depx = query(root, pre), depy = query(root, nex);
105         if (depx > depy) o = pre;
106         else o = nex;
107     }
108     f[x] = o, c[o][x > o] = x; link(o, x); S.insert(x);
109     return query(root, x);
110     }
111     int find_min() {
112     int o = *(++S.begin()), fa = f[o], child = c[o][1];
113     int ans = query(root, o);
114     if (o != root) {
115         cut(o, fa), cut(child, o), link(child, fa), link(root, o);
116         c[o][1] = root, f[root] = o; root = o; f[o] = 0; c[fa][0] = child, f[child] = fa;
117     }
118     return ans;
119     }
120     int find_max() {
121     int o = *(--(--S.end())), fa = f[o], child = c[o][0];
122     int ans = query(root, o);
123     if (o != root) {
124         cut(o, fa), cut(child, o), link(child, fa), link(root, o);
125         c[o][0] = root, f[root] = o; root = o; f[o] = 0; c[fa][1] = child, f[child] = fa;
126     }
127     return ans;
128     }
129     int del_min() {
130     int o = *(++S.begin()), fa = f[o], child = c[o][1];
131     int ans = query(root, o);
132     cut(o, fa), cut(o, child); link(fa, child);
133     f[child] = fa, c[fa][0] = child;
134     S.erase(S.find(o)); f[o] = c[o][1] = c[o][0] = 0;
135     if (root == o) root = child, f[child] = 0;
136     return ans;
137     }
138     int del_max() {
139     int o = *(--(--S.end())), fa = f[o], child = c[o][0];
140     int ans = query(root, o);
141     cut(o, fa), cut(o, child); link(fa, child);
142     f[child] = fa, c[fa][1] = child;
143     S.erase(S.find(o)); f[o] = c[o][0] = c[o][1] = 0;
144     if (root == o) root = child, f[child] = 0;
145     return ans;
146     }
147 }T;
148
149 void work() {
150     scanf("%d", &m);
151     for (int i = 1; i <= m; i++) {
152     scanf("%d", &q[i].opt);
153     if (q[i].opt == 1) {
154         scanf("%d", &q[i].x); a[++top] = q[i].x;
155     }
156     }
157     sort(a+1, a+1+top); top = unique(a+1, a+top+1)-a-1;
158     for (int i = 1; i <= m; i++) {
159     if (q[i].opt == 1) printf("%d\n", T.insert(lower_bound(a+1, a+top+1, q[i].x)-a));
160     else if (q[i].opt == 2) printf("%d\n", T.find_min());
161     else if (q[i].opt == 3) printf("%d\n", T.find_max());
162     else if (q[i].opt == 4) printf("%d\n", T.del_min());
163     else printf("%d\n", T.del_max());
164     }
165 }
166 int main() {
167     work();
168     return 0;
169 }

原文地址:https://www.cnblogs.com/NaVi-Awson/p/8126889.html

时间: 2024-11-08 10:45:45

[HNOI 2017]单旋的相关文章

AC日记——「HNOI2017」单旋 LiBreOJ 2018

#2018. 「HNOI2017」单旋 思路: set+线段树: 代码: #include <bits/stdc++.h> using namespace std; #define maxn 100005 #define maxtree maxn<<2 int val[maxtree],tag[maxtree],L[maxtree],R[maxtree],mid[maxtree]; int op[maxn],ki[maxn],bi[maxn],cnt,size,n,ch[maxn]

bzoj4825 [Hnoi2017]单旋

4825: [Hnoi2017]单旋 Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 520  Solved: 247[Submit][Status][Discuss] Description H 国是一个热爱写代码的国家,那里的人们很小去学校学习写各种各样的数据结构.伸展树(splay)是一种数据 结构,因为代码好写,功能多,效率高,掌握这种数据结构成为了 H 国的必修技能.有一天,邪恶的"卡"带着 他的邪恶的"常数"

【BZOJ4825】[Hnoi2017]单旋 线段树+set

[BZOJ4825][Hnoi2017]单旋 Description H 国是一个热爱写代码的国家,那里的人们很小去学校学习写各种各样的数据结构.伸展树(splay)是一种数据结构,因为代码好写,功能多,效率高,掌握这种数据结构成为了 H 国的必修技能.有一天,邪恶的“卡”带着他的邪恶的“常数”来企图毁灭 H 国.“卡”给 H 国的人洗脑说,splay 如果写成单旋的,将会更快.“卡”称“单旋 splay”为“spaly”.虽说他说的很没道理,但还是有 H 国的人相信了,小 H 就是其中之一,s

BZOJ4825:[HNOI2017]单旋

4825: [Hnoi2017]单旋 Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 550  Solved: 258[Submit][Status][Discuss] Description H 国是一个热爱写代码的国家,那里的人们很小去学校学习写各种各样的数据结构.伸展树(splay)是一种数据 结构,因为代码好写,功能多,效率高,掌握这种数据结构成为了 H 国的必修技能.有一天,邪恶的“卡”带着 他的邪恶的“常数”来企图毁灭 H 国.“卡”给

[HNOI2017]单旋

标签:线段树+set 题解: 此题的标题为splay,所以我们可以排除这道题的正解是splay的可能性.然后我们发现只有最值的单旋,而且,三点一线不需要先旋转父亲.通过手玩我们可以发现,就是把最值直接移到最顶端作为根节点,然后其他的点以及他们之间的父子关系全部都没有变化.于是就只要求深度了. 我们发现,最小值,他没有左子树,而右子树在单旋之后深度不变(-1+1),而其他的点深度全部+1.如果再删掉根节点,全部的点深度-1.于是就可以使用线段树,维护每一个点的深度. 首先输入所有的操作,对于全部的

HNOI2017单旋

单旋 这道题做法贼多,LCT,splay,线段树什么的貌似都行. 像我这种渣渣只会线段树了(高级数据结构学了也不会用). 首先离线所有操作,因为不会有两个点值重复,所以直接离散. 一颗线段树来维护所有点的深度,并将所有值丢进\(set\)中. 插入操作,在set找到前驱后继,前驱没有右儿子就放前驱右儿子,否则放后继左儿子,同时用\(ch\)和\(fa\)假装模拟树的形态. 旋转操作,在\(set\)里找到节点,可以发现旋转操作该点儿子深度不变,其他点深度加一,处理一下父子关系,然后线段树修改区间

[AH2017/HNOI2017]单旋

题目描述 H国是一个热爱写代码的国家,那里的人们很小去学校学习写各种各样的数据结构.伸展树(splay)是一种数据结构,因为代码好写,功能多,效率高,掌握这种数据结构成为了H国的必修技能.有一天,邪恶的"卡"带着他的邪恶的"常数"来企图毁灭H国."卡"给H国的人洗脑说,splay如果写成单旋的,将会更快."卡"称"单旋splay"为"spaly".虽说他说的很没道理,但还是有H国的人相信

[2017.11.29]BZOJ4825[Hnoi2017]单旋

1 #include<bits/stdc++.h> 2 #define M 100010 3 #define RG register 4 #define inf 0x3f3f3f3f 5 using namespace std; 6 bool rev[M]; 7 set<int> tr; 8 set<int>::iterator it; 9 int m,rt,tp,big,cnt,cur,dau,dep,loc,sml,sum,tmp,c[M],fa[M],sz[M],

BZOJ 4825 [Hnoi2017]单旋

题解:LCT维护Splay形态 Splay后发现只会有几个点发生变化,用LCT维护一下就可以了 在Splay中维护siz 还可以用Splay维护DFS序,旋转后DFS序不变,深度以子树为单位变化 天真的我以为直接模拟Splay可以A掉QWQ #include<iostream> #include<cstdio> #include<cstring> #include<map> using namespace std; const int maxn=100009