[ds记录]P3203 弹飞绵羊
一道简单好玩的题,可以加深对 LCT 的理解。
题意简述:给定一座森林,支持两个操作:把某个节点的父亲改成另一个节点、查询某个节点到其对应树树根的距离。
看到有动态连边断边就可以先想 LCT。
LCT 的特点是长于处理序列信息。对于这种查询到根的路径,不用换根,只需 access 后直接查询 Splay 即可。
至于断边,也可以理解为把 x 分离出这个 splay。只需在 splay(x) 后双向断开 x 与其左儿子即可。
连边直接连虚边,因为认父不认子,只需改一次 fa 即可。因为 x 的子树有改变,记得 pushup(x)。
题不难,带来的对 LCT 的启发有:
- 一般连边只用连虚边,能保证 LCT 性质即可。
- 改虚实边仅会通过 access 操作,在普通处理时 access 后仅考虑当前节点所在 splay 即可。所有通过虚边连向 x 的节点的相对位置也会改变,即把 x 连其子树一起换到了其它位置。
access 是 LCT 的所有操作的基础。此题仅使用 access 操作就可解决。
//time : 2022-06-19
//problem url : https://www.luogu.com.cn/problem/P3203
//status : AC
#include <cstdio>
#include <queue>
#define inf 10000005
using namespace std;
const int M = 200005;
int op, x, n, m, y, a[M], k[M];
struct LCT{
#define ls(x) ch[x][0]
#define rs(x) ch[x][1]
#define isroot(x) (ch[fa[x]][0] != x && ch[fa[x]][1] != x)
int sz[M], rt, tot, fa[M], ch[M][2];
void pushup(int x) {sz[x] = sz[ls(x)] + sz[rs(x)] + 1;}
bool get(int x) {return x == rs(fa[x]);}
void rotate(int x){
int y = fa[x], z = fa[y], chk = get(x);
if (!isroot(y)) ch[z][ch[z][1] == y] = x;
ch[y][chk] = ch[x][chk ^ 1]; if(ch[x][chk ^ 1]) fa[ch[x][chk ^ 1]] = y;
fa[y] = x; ch[x][chk ^ 1] = y; fa[x] = z;
pushup(y); pushup(x);
}
void splay(int x){
while(!isroot(x)){
int y = fa[x];
if(!isroot(y)) rotate(get(x) == get(y) ? y : x);
rotate(x);
}
}
void access(int x){
int f = x;
for(int y = 0; x; y = x, x = fa[x]){
splay(x); rs(x) = y; pushup(x);
}
splay(f);
}
void modify(int x, int y){
access(x); splay(x); fa[ch[x][0]] = 0; ch[x][0] = 0;
if(x + y <= n) fa[x] = x+y; pushup(x);
}
int query(int x){
access(x); splay(x); return sz[x];
}
}T;
int main(){
scanf("%d", &n);
for(int i = 1; i <= n; i++){
scanf("%d", &k[i]);
if(i + k[i] <= n) T.fa[i] = i + k[i];
}
scanf("%d", &m);
while(m--){
scanf("%d %d", &op, &x); ++x;
if(op == 1) printf("%d\n", T.query(x));
else scanf("%d", &y), T.modify(x, y);
}
}
本文来自博客园,作者:purplevine,转载请注明原文链接:https://www.cnblogs.com/purplevine/p/16398303.html

浙公网安备 33010602011771号