LCT学习笔记

Link-Cut Tree 是用于维护由一组有根树组成的森林的数据结构,Link-Cut Tree 的基本操作复杂度为均摊 \(O(log2n)\),具体的定义和时间复杂度的证明可以移步《QTREE 解法的一些研究》,这里主要介绍它的基本操作的具体实现。

定义

struct node *null; //定义一个虚拟空节点
struct node {
    node *fa, *c[2]; //fa 代表节点的父亲,c[2]代表节点的左右儿子
    bool rev;        //rev为翻转标记
    node *top;       //top代表节点所在实路径尾部节点的父亲
    int siz;
    node(): fa(null), top(NULL) {c[1] = c[0] = null;
    //将每个点的父亲和左右儿子初始化为虚拟空节点,top为NULL
}

操作

Link-Cut Tree 支持以下几种基本操作:
1、 \(Access(u)\),“访问”某个节点 u,被“访问”过的节点会与根节点之间以路径相连,并且该节点为路径头部(最下端);
2、 \(Evert(u)\),将某个节点 u 置为其所在树的根节点,该操作等价于把该节点到根节点所经过的所有边方向取反;
3、 \(Link(u,v)\),将某两个节点 u 和 v 连接;
4、 \(Cut(u,v)\),将某两个节点 u 和 v 分离;
5、 \(FindRoot(u)\),查找某个节点 u 所在树的根节点;

在实现这些操作之前,先列出基础操作

\(reverse\)

将一个节点翻转

inline void reverse () {
    rev ^= 1;
    swap(c[0], c[1]);
}
\(updata\)

更新节点信息

inline void updata() {
    ma = max(max(c[1]->ma, c[0]->ma), val);
    sum = c[1]->sum + c[0]->sum + val;
}
\(pushdown\)

标记下传

inline void pushdown() {
    if (rev) {
        c[1]->reverse(), c[0]->reverse();
        rev = 0;
    }    
}
\(relation\)

得到节点是哪个儿子

inline bool relation() {
    return fa->c[1] == this;
}
\(rotate\)

splay中的旋转操作,注意这里传递了一个bool值,用来表示将要旋转的这个节点是其父亲节点的哪个儿子。
注意在旋转时更新信息以及下传标记。

inline void rotate(const bool &f) {
    node *o = fa;
    top = o->top;                           
    //保证top的有效值总在splay的根节点上
    o->pushdown(), pushdown();             
    //下传标记
    (fa = o->fa)->c[o->relation()] = this;  
    //将this的fa替换为o的fa,同时代替父亲成为o的fa的儿子
    (o->c[f] = c[!f])->fa = o;             
    //因为this的c[!f]将变成o,所以this的c[!f]变成了o的c[f],同时更新它的fa
    (c[!f] = o)->fa = this;                 
    //o变成了this的c[f]
    o->updata();                            
    //更新o的值
}
\(splay\)

splay操作基本与普通的splay操作相同,注意标记下传和更新

inline void splay() {
    bool f;
    node *o = fa;
    for (pushdown(); o != null; o = fa) {
        if (o->fa == null) {
            rotate(o->c[1] == this);
        } else {
            (f = o->c[1] == this) == (o->fa->c[1] == o) ?
            (o->rotate(f), rotate(f)) :
            (rotate(f), rotate(!f));
        }
    }
    updata();
}

access 操作

\(expose\)

在实现 access 操作前,我们先来实现 expose 操作,它的作用是将当前节点置为其所在路径的头部节点,即切断自该节点向下的部分路径。
注意标记的下传和更新

inline void expose(node* p = null) {
    splay();
    if (c[1] != null) {
        c[1]->fa = null;
        c[1]->top = this;
    }
    (c[1] = p)->fa = this;
    //若p为空,则右子树为空,否则将p所代表的树接在this的右子树位置
    updata();
}

有了expose操作实现access操作就很简单了。

\(access\)

先切断要access的节点的右子树(即切断自该节点向下的部分路径),然后若其所在实路径不包含根,则将当前节点所在的路径与其尾部节点的父节点所在的路径合并,即将路径的向上延长

inline void access() {
    node *x = this;
    for (x->expose(); x->top; x = x->top) {
        x ->top->expose(x);
    }
}

evert 操作

首先执行 Access,将该节点与根节点之间用一条完整的路径连接,然后翻转这条路径即可。

inline void evert() {
    access(); splay(); reverse();
}

把它变成根然后把top设置成this就好辣

inline void link(node *y) {
    y->evert();
    y->top=this;
}

cut 操作

因为这两个点是连在一起的,所以expose一下,再把top断开就好了

inline void cut(node *y) {
    node *x = this;
    x->expose(), y->expose();
    if (x->top == y) x->top = NULL;
    if (y->top == x) y->top = NULL;
}

findroot 操作

access一下,在splay树上找到最左边的儿子,即是这个实路径的尾部节点,同时也是树根,找的时候传一下标记。

inline node* findroot() {
    node *f = this;
    f->access(), f->splay();
    while (f->pushdown(), f->c[0] != null)f = f->c[0];
    f->splay();
    return f;
}

query 和 modify 操作

以查询两个点之间的点权最大值为例。首先在 Node 结构体中存储 max 成员,并在 updata( )中维护它。
首先,如果需要查询某个点到根节点之间的点权最大值,只需先访问这个节点,即 access(u),然后对该节点执行 splay 操作,将其置为其所在splay 的根节点,此时 u 的 max 存储的值即为 u 到其所在树的根节点的路径上的点权最大值。
如果要查询任意两点间的点权最大值,只需要先对其中一个节点执行 evert 操作,将其置为树根,就可以转化为上述情况进行处理。

split

要查询两点间路径上的值,先把一个点置为根,然后查询就好了

inline void split(node *y) {
    y->evert(); access(); splay();
    //此时this节点的值就代表了这一段路径上的值
}

路径上修改也是同理,先split一下,修改this的值,然后打上标记就好辣
要修改某个点的点权值,只需要对该节点执行 splay 操作,将其置为其所在 splay 的根节点,然后直接修改即可,这样可以避免修改时标记的向上传递。

modify(单点)
inline void modify(const int &v) {
    splay();
    val = v;
    updata();
}

init 操作

对虚拟空节点进行初始化,可以根据具体题目要求进行更改

inline void init() {
    null = pool;
    null->fa = null->c[1] = null->c[0] = null;
    null->siz = 0;
}

例题

一棵n个点的树,每个点的初始权值为1。对于这棵树有q个操作,每个操作为以下四种操作之一:
u v c:将u到v的路径上的点的权值都加上自然数c;
u1 v1 u2 v2:将树中原有的边(u1,v1)删除,加入一条新边(u2,v2),保证操作完之后仍然是一棵树;
u v c:将u到v的路径上的点的权值都乘上自然数c;
/ u v:询问u到v的路径上的点的权值和,求出答案对于51061的余数。

链接

bzoj2631

输入

第一行两个整数n,q
接下来n-1行每行两个正整数u,v,描述这棵树
接下来q行,每行描述一个操作

输出

对于每个对应的答案输出一行

样例

输入
3 2
1 2
2 3
* 1 3 4
/ 1 1
输出
4

数据规模和约定

10%的数据保证,\(1<=n,q<=2000\)

另外15%的数据保证,\(1<=n,q<=5*10^4\),没有-操作,并且初始树为一条链

另外35%的数据保证,\(1<=n,q<=5*10^4\),没有-操作

100%的数据保证,\(1<=n,q<=10^5,0<=c<=10^4\)

题解

LCT随便搞搞就好了,模板题

代码

其中的常数优化和读入输出优化参考了xehoth的代码

#include <bits/stdc++.h>
using namespace std;
struct node* null;
const int mod = 51061;
const int N = 100011;
struct node {
    node *fa, *c[2];
    bool rev;
    node *top;
    unsigned int siz, sum, add, mul, val;
    node(): siz(1), sum(1), val(1), mul(1), fa(null) {
        c[0] = c[1] = null;
    }
    inline void cover(const int &m, const int &a) {
        val = (val * m + a) % mod;
        sum = (sum * m + a * siz) % mod;
        add = (add * m + a) % mod;
        mul = mul * m % mod;
    }
    inline void updata() {
        sum = (c[0]->sum + c[1]->sum + val) % mod;
        siz = (c[0]->siz + c[1]->siz + 1) % mod;
    }
    inline void reverse() {
        rev ^= 1;
        swap(c[1], c[0]);
    }
    inline void pushdown() {
        if (rev) {
            c[0]->reverse(), c[1]->reverse(), rev = 0;
        }
        if (add != 0 || mul != 1) {
            c[0]->cover(mul, add), c[1]->cover(mul, add);
            mul = 1, add = 0;
        }
    }
    inline bool relation() {
        return fa->c[1] == this;
    }
    inline void rotate(const bool f) {
        node *o = fa;
        top = o->top;
        o->pushdown(), pushdown();
        (fa = o->fa)->c[o->relation()] = this;
        (o->c[f] = c[!f])->fa = o;
        (c[!f] = o)->fa = this;
        o->updata();
    }
    inline void splay() {
        bool f;
        node *o = fa;
        for (pushdown(); o != null; o = fa) {
            if (o->fa == null) {
                rotate(o->c[1] == this);
            } else {
                (f = o->c[1] == this) == (o->fa->c[1] == o)
                ? (o->rotate(f), rotate(f)) : (rotate(f), rotate(!f));
            }
        }
        updata();
    }
    inline void expose(node * p = null) {
        splay();
        if (c[1] != null) {
            c[1]->top = this;
            c[1]->fa = null;
        }
        (c[1] = p)->fa = this;
        updata();
    }
    inline void access() {
        node *x = this;
        for (x->expose(); x->top; x = x->top)
            x->top->expose(x);
    }
    inline void evert() {
        access(); splay(); reverse();
    }
    inline void link(node *y) {
        y->evert();
        y->top = this;
    }
    inline void cut(node *y) {
        node *x = this;
        x->expose(), y->expose();
        if (x->top == y) x->top = NULL;
        if (y->top == x) y->top = NULL;
    }
    inline node* findroot() {
        node *f = this;
        f->access(), f->splay();
        while (f->pushdown(), f->c[0] != null)f = f->c[0];
        f->splay();
        return f;
    }
    inline void split(node *y) {
        y->evert(); access(); splay();
    }
} pool[N];
inline void init() {
    null = pool;
    null->fa = null;
    null->mul = 1;
    null->add = null->siz = null->val = null->sum = 0;
}
inline char nextChar() {
    static const int IN_LEN = 1000000;
    static char buf[IN_LEN], *s, *t;
    if (s == t) {
        t = (s = buf) + fread(buf, 1, IN_LEN, stdin);
        if (s == t) return -1;
    }
    return *s++;
}

inline int read() {
    static int x = 0;
    static char c;
    for (x = 0, c = nextChar(); !isdigit(c); c = nextChar());
    for (; isdigit(c); c = nextChar())
        x = (x + (4 * x) << 1) + (c ^ '0');
    return x;
}

const int OUT_LEN = 10000000;
char obuf[OUT_LEN], *oh = obuf;

template<class T>
inline void print(T x) {
    static int buf[30], cnt;
    if (x == 0) {
        *oh++ = '0';
    } else {
        if (x < 0) *oh++ = '-', x = -x;
        register int cnt = 0;
        for (cnt = 0; x; x /= 10) buf[++cnt] = x % 10 + 48;
        while (cnt) *oh++ = buf[cnt--];
    }
}

template<class T>
inline void println(T x) {
    print(x), *oh++ = '\n';
}

inline void flush() {
    fwrite(obuf, 1, oh - obuf, stdout);
}

int n, q;

int main() {
#ifndef ONLINE_JUDGE
    freopen("in.in", "r", stdin);
#endif
    init();
    n = read(), q = read();
    for (register int i = 1; i <= n; i++) pool[i] = node();
    char s;
    register int x, y, z;
    for (register int i = 1; i < n; i++) (pool + read())->link(pool + read());
    for (register int i = 1; i <= q; i++) {
        s = nextChar(), x = read(), y = read();
        switch (s) {
        case '+':
            z = read(), (pool + x)->split(pool + y);
            (pool + x)->cover(1, z);
            break;
        case '-':
            (pool + x)->cut(pool + y),
            x = read(), y = read(),
            (pool + x)->link(pool + y);
            break;
        case '*':
            z = read(), (pool + x)->split(pool + y);
            (pool + x)->cover(z, 0);
            break;
        case '/':
            (pool + x)->split(pool + y), println((pool + x)->sum);
            break;
        }
    }
    flush();
    return 0;
}

转载于:https://www.cnblogs.com/thhyj/p/11125274.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值