一,原理简述
- 保证每一次操作复杂度 O ( l o g n ) O(log~n) O(log n) 的做法是把当前操作对象旋转到树根
- BST 性质(拓展一下就是顺序的特殊性可以维护)
BST是这样的一棵二叉树:对于任意节点,有:
- 它的值不小于它左子树任意节点的值
- 它的值不大于它右子树任意节点的值
这个性质就是BST性质,满足这个性质的二叉树就是一棵BST。
BST的中序遍历序列是一个非严格单调递增的序列。
二,注意点
1,唯一的up在spin里
2,get_v_byk 注意特别关注,在加入cnt之后非常不一般
3,up中的子树附加值是cnt
4,splay操作逻辑结果位运算注意括号
5,注意pain操作的不同操作位置,举个例子,倒数第2个操作一但和最后一步写反,就会tle
6,注意splay的位置
7,plug计数由1开始,++idx
(不是idx++
)
8,plug时无根直接返回,就是tr[p]
怎样的那个
9,codeblocks括号匹配魔性,不要顺着感觉打括号,要思考打出括号的最佳方案
10,考虑使用宏解决除plug位运算外的部分码量(后期熟了就好了可能)
步骤拆析
1,splay基本元
- 注意
init
初始化,子树大小,计数位置都不能是0 - 一般插入哨兵 2个,INF 和 —INF
- 子树编号: 0为左子树,1为右子树
struct node
{
int s[2];
int v,p,siz,cnt;
void init(int _p,int _v)
{
p= _p;
v= _v;
siz= 1;
cnt= 1;
}
}tr[N];
2,up 向上更新操作
由题目的具体要求决定
在保序BST 中:
void up(int p)
{
tr[p].siz= tr[tr[p].s[1]].siz+ tr[tr[p].s[0]].siz +tr[p].cnt;
}
在文艺平衡树中需要翻转标记:
3,spin/rotain(左右旋兼容)
-
思考角度:
(右/左)旋: 拉着 x − y x - y x−y 这条链子向(右/左)扯 ,可能把它拽下来或者拖上去之类,结合实际经验还是很好理解的啊 -
形式化思考:
由 x , y , z x,y,z x,y,z 构成的三层结构,按照树中高度排序, d e p ( z ) < d e p ( y ) < d e p ( x ) dep(z)<dep(y)< dep(x) dep(z)<dep(y)<dep(x) -
作用: 把传参 x 向上旋转,同时保证 bst
-
注意: up的位置(这是唯一的up)
void spin(int x)
{
int y= tr[x].p;
int z= tr[y].p;
int k= tr[y].s[1]==x;
tr[z].s[tr[z].s[1]==y] =x; tr[x].p= z;
tr[tr[x].s[k^1]].p= y; tr[y].s[k]= tr[x].s[k^1];
tr[x].s[k^1]= y; tr[y].p= x;
up(y); up(x);
}
4,spaly(关键!!!)
type 1, 无折型(直线型)
type 2 ,带折型(曲线型)
- 传参意义: 把 x 旋转到 k 下面
- 小知识: s p i n ( x ) spin (x) spin(x) 和 s p i n ( y ) spin(y) spin(y) 换了顺序也无妨,可是时间复杂度差别很大
void splay(int x,int k)
{
while (tr[x].p!=k)
{
int y= tr[x].p;
int z= tr[y].p;
if(z!=k)
{
if((tr[y].s[1]==x) ^ (tr[z].s[1]==y))spin(x);
else spin(y);
}
spin(x);
}
if(!k)root= x;
}
5, plug (插入)
插入节点编号动态分配, val作为排序关键词,注意保证bst性质
void plug(int val)
{
int u= root; int p =0;
while (u && tr[u].v!=val) p=u, u= tr[u].s[val>tr[u].v];
if(u)tr[u].cnt++;
else
{
u= ++idx;
if(p)tr[p].s [val> tr[p].v] = u;
tr[u].init(p,val);
}
splay(u,0);
}