自存:用fhq treap维护lct
煮波太菜了所以记不住splay怎么旋。
这篇博客讲了怎么用fhq维护lct。跑的也不算特别慢。
然后网上就不太能找到其他的了。我会尽力,说的详细一点。
然后代码里也有一些注释。
由于我水平有限,可能会有一些理解不太对的地方?欢迎指正。
fhq treap的复杂度正确是基于随机化的,而lct的复杂度是基于势能分析的。虽然一般fhq treap的权值会在最初的时候赋,而且后面不会改。但是显然在每次合并的时候随机一个权值,正确性也是没问题的。
那么假如我们设一个权值cnt表示x所有轻子树的size和+1,(感觉一下肯定会希望这个权值和比较大的那边在上面)合并的时候设两个子树内所有cnt的和分别为\(sum_{x}, sum_{y}\),那么以\(\frac{sum_{x}}{sum_{x} + sum_{y}}\) 的概率让\(x\)做根,直观感受上是很对的。
这就是无旋treap维护lct的基本思想。
具体实现的话,需要实现一个自下而上的分裂,平衡树找根,合并,并且需要同时维护儿子与父亲的关系,加减虚边的话还需要修改cnt。
具体的细节可以看代码注释。
题外话:煮波,煮波,煮波,煮波你怎么调了这么久啊。
原来lct从一开始就是对的……其他地方唐了哈哈哈哈哈哈。
所以建议大家lct的题如果感觉lct没问题了可以看看其他地方有没有挂
放一个lct模板题的代码
#include <bits/stdc++.h>
#define L(i,j,k) for(int i = (j); i <= (k); ++i)
#define R(i,j,k) for(int i = (j); i >= (k); --i)
#define ll long long
#define sz(a) (int)(a).size()
#define pb push_back
#define mk make_pair
#define fir first
#define sec second
using namespace std;
const int N = 1e5 + 5;
struct fhq{
int fa, ls, rs;// 记录父亲(可能是虚父亲),左儿子,右儿子
int xr, val;//xr表示维护的区间的xor,val是自己的值
unsigned int cnt, sum;
bool rev;// 翻转标记
}e[N];
// 注意:同时维护父亲儿子关系,并且虚边改变时要改变cnt值
#define ls(x) e[x].ls
#define rs(x) e[x].rs
#define fa(x) e[x].fa
bool isroot(int x){return ls(e[x].fa) != x && rs(e[x].fa) != x;}
void upd(int x){
if(x == 0) return;
e[x].sum = e[ls(x)].sum + e[rs(x)].sum + e[x].cnt;
e[x].xr = e[ls(x)].xr ^ e[rs(x)].xr ^ e[x].val;
}
void frev(int x){
if(x == 0) return;
swap(e[x].ls, e[x].rs); e[x].rev^=1;
}//区间翻转
void down(int x){
if(e[x].rev){
frev(ls(x)); frev(rs(x));
e[x].rev = 0;
}
}
void prev(int x){//提前将x的所有实父亲pushdown
upd(x);
if(!isroot(x)) prev(fa(x));
down(x);
}
int spilt(int x, int &ql, int &qr){//自下而上地平衡树分裂
if(x == 0) return ql = qr = 0, 0;
prev(x);
int f = fa(x), p = x;
qr = rs(p); rs(p) = 0; fa(qr) = 0; ql = p; upd(p);
// p 为记录的儿子,用于判断当前点f是在x前面还是后面
// 注意分裂的时候要同时维护儿子和父亲的关系
// 可以画图来确认形态
while(true){
if(ls(f) == p) {ls(f) = qr; fa(qr) = f;qr = f;}
else if(rs(f) == p){rs(f) = ql; fa(ql) = f; ql = f;}
else break;//说明f已经不在这颗平衡树里了
upd(f);
p = f; f = fa(f); // 注意这里p的父亲可能已经改变
}
fa(ql) = fa(qr) = 0;
return f;// 返回的是整个平衡树的虚父亲
}
mt19937 sto(time(0)^*new int);
int merge(int x, int y){// 除了比较时的权值,其他与普通fhq合并相同
if(x == 0 || y == 0) return x + y;
if(sto() % (e[x].sum + e[y].sum) < e[x].sum){
down(x); e[x].rs = merge(e[x].rs, y); fa(e[x].rs) = x; upd(x); return x;
}
down(y); e[y].ls = merge(x, e[y].ls); fa(e[y].ls) = y; upd(y); return y;
}
int root_(int x){// 找到x所在平衡树中的根节点
if(x == 0) return 0;
while(!isroot(x)) x = fa(x);
return x;
}
void save(int x){
if(x == 0) return;
while(!isroot(x)){upd(x); x = fa(x);}
upd(x);
}
// 终于到lct了!
int access(int x){
int las = 0;// 之前的链合并出来的平衡树,要往上合并到根
int ai, bi, fq;
while(x){// x代表当前所在的点
fq = spilt(x, ai, bi); // 将x所在链从x处分为两段
e[x].cnt += e[bi].sum; e[x].cnt -= e[las].sum;
save(x);//因为更新了x的cnt, 所以其所有祖先也需要被更新
e[bi].fa = x;
las = merge(ai, las);
fa(las) = fq; x = fq;
}
return las; // 返回值为x到根的链的平衡树
}
// 后面的部分就都差不多了
void makeroot(int x){frev(access(x));}
int SPILT(int x, int y){makeroot(x); return access(y);}
set <pair<int, int> > s;
void link(int x, int y){
makeroot(x);
access(y);
int R;
if( (R = root_(x)) == root_(y)) return;
s.insert(minmax(x, y));
fa(R) = y; e[y].cnt += e[R].sum;
save(y);
}
void cut(int x, int y){
if(s.count(minmax(x, y)) == 0) return;
s.erase(minmax(x, y));
makeroot(x); access(y);// 理论而言,这时候这颗平衡树只有两个点
int ql, qr;
spilt(x, ql, qr);
}
int n, m;
int main(){
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n >> m;
L(i, 1, n){
cin >> e[i].val; e[i].xr = e[i].val; e[i].sum = e[i].cnt = 1;
}
int op, x, y;
while(m--){
cin >> op >> x >> y;
if(op == 0){
cout << e[SPILT(x, y)].xr << "\n";
}
if(op == 1){
link(x, y);
}
if(op == 2){
cut(x, y);
}
if(op == 3){
access(x);
e[x].val = y; save(x);
}
}
return 0;
}

浙公网安备 33010602011771号