• Link Cut Tree学习笔记


    动态树问题和Link Cut Tree

      动态树问题是一类要求维护一个有根树森林,支持对树的分割, 合并等操作的问题。

      Link Cut Tree林可砍树?简称LCT)是解决这一类问题的一种数据结构。

    一些无聊的定义

      Link Cut Tree维护的是动态森林中每棵树的任意链剖分。

      Preferred Child,每个点偏好的子节点,对于每个点,要么它没有,要么它只有一个。

      Preferred Edge,连接每个点和它偏好的子节点的边,以下简称为实边

      相对地,对于非实边的边,以下简称为虚边。

      Preferred Path,由Preferred Edge 连接成的不可再延伸的路径。以下简称为实链。特殊地,如果与一个点相连的所有边都是虚边,那么这一个点独自构成一条实链。

      显然,所有实链覆盖所有的点。

      对于每条实链,LCT用一颗Splay对节点按深度为关键字进行维护。

    //接下来会默认读者能熟练地敲打Splay的板子

      为了方便直接用Splay森林来维护。对于每棵Splay,它还需要维护它的维护的实链的顶端(深度最小的点)的父节点,这个记在根结点上。

    access操作

      access操作是将某个节点$x$到根的路径变为实链。

      由图中可以看出,access操作可以看成以下几个操作的反复进行:

    1. 断掉当前实链中当前点和比它深的点之间的实边
    2. 合并当前实链和上一条实链
    3. 访问实链顶端的父节点 

      当当前点为空的时候结束,不存在上一条实链的时候它为空。

      显然这样操作恰好将指定点到根的路径变为实链了。

    1 void access(SplayNode *p) {
    2     SplayNode* q = NULL;
    3     while (p) {
    4         splay(p);
    5         q = p, p = p->fa;
    6     }
    7 }

    换根操作

      将一棵树的根变为指定点。

      考虑换根操作的影响:

      只是旧根到新根的路径被翻转了。那就先对新根执行access操作,然后打反转标记就好了。

    1 void mkroot(SplayNode* node) {
    2     access(node);
    3     splay(node);
    4     node->rev ^= 1;
    5 }

    link和cut操作

      link操作是连接两个不连通的点,cut操作是删除一条原有的边。

      考虑link操作,将其中一个作为根然后向另一个点连接一条虚边即可。

      考虑cut操作,将其中一个作为根,然后对另一个点做access操作,这样恰好根所在的Splay中的后继是另一个点,Splay树上断掉这条边即可。

     1 void link(int u, int v) {
     2     SplayNode* p = pool + u, *q = pool + v;
     3     if(isConnected(p, q))    return ;
     4     mkroot(q);
     5     q->fa = p, splay(q);
     6 }
     7         
     8 void cut(int u, int v) {
     9     SplayNode* p = pool + u, *q = pool + v;
    10     mkroot(p);
    11     access(q);
    12     splay(q);
    13     q->ch[0] = p->fa = NULL;
    14 }

    例1 Cave 洞穴勘测

    题目大意

      给定一个动态森林,要求支持加边、删边和询问两点之间的连通性。

      判断两点之间是否连通,等价于它们所在的树的根相同。

      所以考虑如何找一个点所在树的根。

      首先access这个点,然后把它伸展到根,然后暴力跳左子树就好了。

    Code

      1 /**
      2  * bzoj
      3  * Problem#2049
      4  * Accepted
      5  * Time: 1976ms
      6  * Memory: 1500k
      7  */
      8 #include <bits/stdc++.h>
      9 using namespace std;
     10 typedef bool boolean;
     11 
     12 typedef class SplayNode {
     13     public:
     14         boolean rev;
     15         SplayNode *ch[2];
     16         SplayNode *fa;
     17         
     18         SplayNode():rev(0), ch({NULL, NULL}), fa(NULL) {        }
     19         
     20         boolean isRoot() {
     21             if(fa == NULL)    return true;
     22             return fa->ch[0] != this && fa->ch[1] != this;
     23         }
     24         
     25         void pushDown() {
     26             swap(ch[0], ch[1]);
     27             if(ch[0])    ch[0]->rev ^= 1;
     28             if(ch[1])    ch[1]->rev ^= 1;
     29             rev = 0;
     30         }
     31 }SplayNode;
     32 
     33 typedef class LinkCutTree {
     34     public:
     35         SplayNode *pool;
     36         
     37         LinkCutTree():pool(NULL) {        }
     38         LinkCutTree(int n) {
     39             pool = new SplayNode[(n + 1)];
     40         }
     41         
     42         void rotate(SplayNode* node) {
     43             SplayNode* fa = node->fa;
     44             boolean aFlag = fa->isRoot();
     45             int d = fa->ch[1] == node;
     46             fa->ch[d] = node->ch[d ^ 1];
     47             node->fa = fa->fa;
     48             fa->fa = node;
     49             node->ch[d ^ 1] = fa;
     50             if(fa->ch[d])    fa->ch[d]->fa = fa;
     51             if(!aFlag) node->fa->ch[node->fa->ch[1] == fa] = node;
     52         }
     53         
     54         SplayNode* s[10005];
     55         void splay(SplayNode* node) {
     56             int top = 0;
     57             s[++ top] = node;
     58             for (SplayNode* p = node; !p -> isRoot(); p = p->fa) s[++ top] = p -> fa; 
     59             for (int i = top; i; -- i) if (s[i] -> rev) s[i] -> pushDown();
     60             while(!node->isRoot()) {
     61                 SplayNode *fa = node->fa, *ffa = fa->fa;
     62                 if(fa->isRoot()) {
     63                     rotate(node);
     64                     break;
     65                 }
     66                 int d1 = (fa->ch[1] == node), d2 = ffa->ch[1] == fa;
     67                 if(d1 == d2)
     68                     rotate(fa);
     69                 else
     70                     rotate(node);
     71                 rotate(node);
     72             }
     73         }
     74         
     75         SplayNode* findSplayRoot(SplayNode* node) {
     76             access(node);
     77 //            while(node->fa)    node = node->fa;
     78             splay(node);
     79             while(node->ch[0])    node = node->ch[0];
     80             return node;
     81         }
     82         
     83         void access(SplayNode* node) {
     84             SplayNode* p = NULL;
     85             do {
     86                 splay(node);
     87                 node->ch[1] = p;
     88                 p = node;
     89                 node = node->fa;
     90             } while (node);
     91         }
     92         
     93         boolean isConnected(SplayNode* p, SplayNode* q) {
     94             return findSplayRoot(p) == findSplayRoot(q);
     95         }
     96         
     97         void mkroot(SplayNode* node) {
     98             access(node);
     99             splay(node);
    100             node->rev ^= 1;
    101         }
    102         
    103         void link(int u, int v) {
    104             SplayNode* p = pool + u, *q = pool + v;
    105             if(isConnected(p, q))    return ;
    106             mkroot(q);
    107             q->fa = p, splay(q);
    108         }
    109         
    110         void cut(int u, int v) {
    111             SplayNode* p = pool + u, *q = pool + v;
    112             mkroot(p);
    113             access(q);
    114             splay(q);
    115             q->ch[0] = p->fa = NULL;
    116         }
    117         
    118         boolean isConnected(int u, int v) {
    119             return isConnected(pool + u, pool + v);
    120         }
    121 }LinkCutTree;
    122 
    123 int n, m;
    124 LinkCutTree lct;
    125 char buf[15];
    126 int u, v;
    127 inline void solve() {
    128     scanf("%d%d", &n, &m);
    129     lct = LinkCutTree(n);
    130     while(m--) {
    131         scanf("%s%d%d", buf, &u, &v);
    132         switch(buf[0]) {
    133             case 'C':
    134                 lct.link(u, v);
    135                 break;
    136             case 'D':
    137                 lct.cut(u, v);
    138                 break;
    139             case 'Q':
    140                 puts((lct.isConnected(u, v)) ? ("Yes") : ("No"));
    141                 break;
    142         }
    143     }
    144 }
    145 
    146 int main() {
    147     solve();
    148     return 0;
    149 }
    View Code

    时间复杂度证明

      如果学习一个数据结构能证明它的时间复杂度那就再好不过了。

      对于LCT的操作,基本上是access,然后加上某些$O(1)$的操作或者均摊$O(log n)$的Splay操作。所以只需要证明access的时间复杂度是$O(log n)$即可。

      在开始证明前,你需要知道Splay的时间复杂度和一些常识性的东西。

    定理1 对于操作splay(x),设操作前$x$的子树大小为$siz[x]$,操作后的子树大小为$siz'[x]$,splay(x)的均摊时间复杂度小于等于$3(log siz'[x] - log siz[x]) + 1$

       证明略去(其实是我不会)

      下面一个是关于轻重链剖分的常识,证明可以看这篇随笔

    定理2 一条根节点到叶节点的路径上,轻边的条数不超过$log_{2}n$条

       然后还有一个需要证明的事情。

    定理3 Preferred Child的改变次数均摊为$O(log_{2}n)$次

      证明 因为每次Preferred Child的改变时会导致实边发生改变。所以考察实边改变的次数即可。

      这一部分可以分为两种情况:

    • 一条轻虚边被改为轻实边。
    • 一条重虚边被改为重实边。 

      因为轻边总数不超过$log_{2}n$条,所以第一部分的改变次数不超过$log_{2}n$次。

      考虑重虚边被改为重实边的情况。

      考虑每一条重边,它从实变为虚,虚变为实的过程是交替进行的。所以它被改为实边的次数不会超过它从实边被改为虚边的次数加1。

      当一条重实边被改为重虚边会导致一条轻虚边变为轻实边,同时我们证明了轻虚边变为轻实边的均摊次数不超过$O(log_{2}n)$,所以一条重虚边被改为重实边的次数也不超过$O(log_{2}n)$。

      因此定理得证。

    定理4 access的均摊时间复杂度为$O(log_{2}n)$

      证明 设每次设为当前点的点依次为$v_{0}, v_{1}, v_{2},cdots,v_{k}$。

      那么有:

    $widehat{cost}leqslant 3left (sum_{i = 1}^{k}log siz[v_{i}] - log siz[v_{i - 1}] + 1 ight ) + log siz[v_{0}]$

      所以化简后再根据定理3,可得:

    $widehat{cost}leqslant 6log{n}$

    Link Cut Tree维护链上信息

      对于有关边权的链上信息考虑为每条边建一个虚点。然后和它两端连边。

      查询链上信息可以通过把一个点变为根,access另一个点后的splay中的信息就是这条链上的信息。

      但是有些装逼爱好者觉得这样不能满足他们的欲望,想出了一种不用建虚点的方法。很开心的是细节超多,看到neither_nor的某道题写了200+行,想想得到结论——装逼是要有代价的。

      这个方法的讲解:neither_nor神犇的博客

    例2 魔法森林

    题目大意

      一张图有$n$个点,$m$条边,第$i$条边有两个边权$a_{i}$和$b_{i}$。定义从1号点走到$n$号点的代价是经过边的所有$a$边权的最大值加上所有$b$边权的最大值。

      问从1号点走到$n$号点的最小代价。

      高维问题常见思路是降维,因此考虑按照$a$边权排序,然后枚举。

      假设图开始是空的,然后依次加入每一条边。

    • 如果边的两端未连通,直接加就好了。
    • 如果边的两端连通,那么会形成环。根据人生的经验和图论的哲理,可以知道,环是这两点在树上的简单路径再加上这一条边。那么找到换上$b$边权最大的一条边比较它和当前边的$b$边权。如果当前边的$b$边权更小,那么就把树上的那条边cut掉,然后把这条新边加上。

      如果在某个时候1和$n$连通,就查询一下它们之间的简单路径中$a$边权和$b$边权的最大值就好了。

      由于这里的“删边”不会改变连通性(因为马上你会加一条边),所以可以直接用并查集维护连通性。

    Code

      1 /**
      2  * uoj
      3  * Problem#3
      4  * Accepted
      5  * Time: 2287ms
      6  * Memory: 11208k
      7  */
      8 #include <bits/stdc++.h>
      9 using namespace std;
     10 typedef bool boolean;
     11 
     12 typedef class SplayNode {
     13     public:
     14         boolean rev;
     15         int ea, eb, idx;
     16         int ma, mb, ib;
     17         SplayNode *ch[2];
     18         SplayNode *fa;
     19 
     20         SplayNode():rev(false), ea(0), eb(0), idx(0), ma(0), mb(0), ib(0), ch({NULL, NULL}), fa(NULL) {    }
     21         
     22         void pushUp() {
     23             ma = ea, mb = eb, ib = idx;
     24             for (int i = 0; i < 2; i++)
     25                 if (ch[i]) {
     26                     (ch[i]->ma > ma) ? (ma = ch[i]->ma) : (0);
     27                     (ch[i]->mb > mb) ? (mb = ch[i]->mb, ib = ch[i]->ib) : (0);
     28                 }
     29         }
     30 
     31         void pushDown() {
     32             swap(ch[0], ch[1]);
     33             if (ch[0])    ch[0]->rev ^= 1;
     34             if (ch[1])    ch[1]->rev ^= 1;
     35             rev = false;
     36         }
     37 
     38         boolean isroot() {
     39             return !fa || (fa->ch[0] != this && fa->ch[1] != this);
     40         }
     41 
     42         int which() {
     43             return (!isroot()) ? (fa->ch[1] == this) : (-1);
     44         }
     45 }SplayNode;
     46 
     47 typedef class LinkCutTree {
     48     public:
     49         SplayNode* pool;
     50         SplayNode** s;
     51         int top, n;
     52 
     53         LinkCutTree() {    }
     54         LinkCutTree(int n, int m):n(n) {
     55             pool = new SplayNode[(n + m + 1)];
     56             s = new SplayNode*[(n + m + 1)];
     57         }
     58 
     59         void rotate(SplayNode* p) {
     60             int d = p->which(), df = p->fa->which();
     61             SplayNode* fa = p->fa, *ffa = fa->fa, *ls = p->ch[d ^ 1];
     62             fa->ch[d] = ls, (ls) ? (ls->fa = fa) : (0);
     63             p->fa = ffa, (~df) ? (ffa->ch[df] = p) : (0);
     64             p->ch[d ^ 1] = fa, fa->fa = p;
     65             fa->pushUp(), p->pushUp();
     66         }
     67 
     68         void splay(SplayNode* p) {
     69             SplayNode *np = p;
     70             for (top = 0; s[++top] = np, !np->isroot(); np = np->fa);
     71             for ( ; top; top--)
     72                 if (s[top]->rev)
     73                     s[top]->pushDown();
     74             for ( ; !p->isroot(); rotate(p))
     75                 if (!p->fa->isroot())
     76                     rotate((p->which() == p->fa->which()) ? (p->fa) : (p));
     77         }
     78 
     79         void access(SplayNode *p) {
     80             SplayNode* q = NULL;
     81             while (p) {
     82                 splay(p);
     83                 p->ch[1] = q;
     84                 p->pushUp();
     85                 q = p, p = p->fa;
     86             }
     87         }
     88 
     89         void mkroot(SplayNode *p) {
     90             access(p);
     91             splay(p);
     92             p->rev ^= 1;
     93         }
     94 
     95         void link(SplayNode* p, SplayNode* q) {
     96             mkroot(q);
     97             q->fa = p;
     98         }
     99 
    100         void cut(SplayNode* p, SplayNode* q) {
    101             mkroot(p);
    102             access(q);
    103             p->ch[1] = q->fa = NULL;
    104             p->pushUp();
    105         }
    106 
    107         SplayNode* query(int u, int v) {
    108             SplayNode* p = pool + u, *q = pool + v;
    109             mkroot(p), access(q);
    110             splay(p);
    111             return p;
    112         }
    113 
    114         void link(int u, int v, int a, int b, int idx) {
    115             SplayNode* p = pool + u, *q = pool + v, *es = pool + n + idx;
    116             es->ea = es->ma = a, es->eb = es->mb = b, es->ib = es->idx = idx;
    117             link(p, es), link(es, q);
    118         }
    119 
    120         void cut(int u, int v, int idx) {
    121             SplayNode *p = pool + u, *q = pool + v, *es = pool + n + idx;
    122             cut(p, es), cut(es, q);
    123         }
    124         
    125         void debugOut(SplayNode* p) {
    126             if (!p)    return;
    127             fprintf(stderr, "%d (fa: %d, ea: %d, eb: %d, ma: %d, mb: %d, idx: %d, ib: %d, flag: %d) {", p - pool, (!p->fa) ? (0) : (p->fa - pool), p->ea, p->eb, p->ma, p->mb, p->idx, p->ib, p->rev);
    128             debugOut(p->ch[0]);
    129             fprintf(stderr, ", ");
    130             debugOut(p->ch[1]);
    131             fprintf(stderr, "}");
    132         }
    133         
    134         void debugOut() {
    135             fprintf(stderr, "--------------------
    ");
    136             for (int i = 1; i <= 7; i++)
    137                 if (pool[i].isroot())
    138                     debugOut(pool + i), fprintf(stderr, "
    ");
    139         }
    140 }LinkCutTree;
    141 
    142 typedef class Edge {
    143     public:
    144         int u, v, a, b;
    145 
    146         boolean operator < (Edge b) const {
    147             return a < b.a;
    148         }
    149 }Edge;
    150 
    151 int n, m;
    152 int res = 211985;
    153 int* f;
    154 Edge* es;
    155 LinkCutTree lct;
    156 
    157 int find ( int x )  {
    158     while ( x != f [x] )  x = f [x] = f [f [x]] ;
    159     return x ;    
    160 }
    161 
    162 inline void init() {
    163     scanf("%d%d", &n, &m);
    164     f = new int[(n + 1)];
    165     es = new Edge[(m + 1)];
    166     lct = LinkCutTree(n, m);
    167     for (int i = 1; i <= n; i++)
    168         f [i] = i ;
    169     for (int i = 1; i <= m; i++)
    170         scanf("%d%d%d%d", &es[i].u, &es[i].v, &es[i].a, &es[i].b);
    171 }
    172 
    173 inline void solve() {
    174     SplayNode* p;
    175     sort(es + 1, es + m + 1);
    176     for (int i = 1, u, v, a, b; i <= m; i++) {
    177         u = es[i].u, v = es[i].v, a = es[i].a, b = es[i].b;
    178         if (u == v)    continue;
    179         if (find(u) != find(v))  {
    180             lct.link(u, v, a, b, i);//, cerr << u << " " << v << endl;
    181             f [find ( u )] = find ( v ) ;
    182         }
    183         else {
    184 //            cerr << "R:" << u << " " << v << endl; 
    185             p = lct.query(u, v);
    186             if (p->mb > b) {
    187                 lct.cut(es[p->ib].u, es[p->ib].v, p->ib);
    188                 lct.link(u, v, a, b, i);
    189             }
    190         }
    191         if (find ( 1 ) == find (n)) {
    192             p = lct.query(1, n);
    193             res = min(res, p->ma + p->mb);
    194 //            cerr << "Q" << endl;
    195         }
    196 //        lct.debugOut();
    197     }
    198     printf("%d
    ", (res == 211985) ? (-1) : (res));
    199 }
    200 
    201 int main() {
    202     srand(233);
    203     init();
    204     solve();
    205     return 0;
    206 }
    View Code

    Link Cut Tree维护子树信息

      首先来说说一些无聊的定义。

      众所周知,LCT维护的是一个动态森林的链剖分。

      定义一个点的LCT子树是这个点在Splay上的子树。

      所以LCT子树可能并不是原树中的子树,它可能包含这个点的祖先。

      一个点的所有虚子树是在Splay中所有与它通过虚边相连的点的LCT子树。

      一个点的所有实子树是在Splay中它的左右子树。

       因此一个点的LCT子树等于它的所有虚子树加上它的所有实子树和它自己。

    某个很有用的性质 将一个点access后,它的虚子树信息再加上它自己的信息就是它的在原树中的子树信息。

      因为access这个点之后它就没有Preferred Child,它的所有子节点都和它通过虚边相连。它的子节点的LCT子树不包含后代,所以恰好是原树中的子树信息。

      考虑什么时候虚子树信息会改变:

    • 当access一个点后
    • 当进行link和cut操作时

      情况1很好处理,断开一条实边的时候加上它的LCT子树信息,添加一条实边的时候减去它的LCT子树信息。

      看起来现在需要维护LCT子树信息,也就是说修改后还需要将信息上传。

      但是access进行断边的时候能够保证当前点是Splay的根,所以通常不用上传任何信息(因为通常它的LCT子树信息不会改变)。

      考虑link操作,我们会添加一条虚边,它会改变一个点的子树信息以及它的祖先的子树信息。

      所以我们先access它,然后把它伸展到根,于是就巧妙地避开了更新祖先的子树信息。

      继续考虑cut操作,我们会断掉1条边,可以用同样的方法进行处理。

    注意事项

    • Splay的形态发生改变时会改变LCT子树信息但是不会改变虚子树信息
    • 发生虚实边的改变时才会导致虚子树信息发生改变
    • 修改时要考虑信息上传的问题,如果难以上传可以考虑避开上传,或者直接进行链上修改。

    例3 大融合

    题目大意

      维护一个动态森林,要求支持

    1. 添加一条边
    2. 询问经过一条边的所有简单路径数

      假如是一个静态森林,那么如果处理询问操作?

      这条边会把树分成两部分,对吧?答案是这两个部分的点数的乘积。

      考虑如何在动态森林中维护子树大小,还是很显然的。

      那么如何算答案?

      把其中一个变成根,然后就可以计算两部分的点数了。

    Code

      1 /**
      2  * bzoj
      3  * Problem#4530
      4  * Accepted
      5  * Time: 1124ms
      6  * Memory: 3488k
      7  */
      8 #include <bits/stdc++.h>
      9 #ifndef WIN32
     10 #define Auto "%lld"
     11 #else
     12 #define Auto "%I64d"
     13 #endif
     14 using namespace std;
     15 typedef bool boolean;
     16 
     17 #define ll long long
     18 
     19 typedef class SplayNode {
     20     public:
     21         boolean rev;
     22         int ls, vs;
     23         SplayNode* ch[2];
     24         SplayNode* fa;
     25 
     26         SplayNode():rev(false), ls(1), vs(0), ch({NULL, NULL}), fa(NULL) {    }
     27         
     28         void pushUp() {
     29             ls = 1 + vs;
     30             if (ch[0])    ls += ch[0]->ls;
     31             if (ch[1])    ls += ch[1]->ls;
     32         }
     33 
     34         void pushDown() {
     35             swap(ch[0], ch[1]);
     36             if (ch[0])    ch[0]->rev ^= 1;
     37             if (ch[1])    ch[1]->rev ^= 1;
     38             rev = false;
     39         }
     40 
     41         boolean isRoot() {
     42             if (!fa)    return true;
     43             return fa->ch[0] != this && fa->ch[1] != this;
     44         }        
     45 }SplayNode;
     46 
     47 typedef class LinkCutTree {
     48     public:
     49         int top;
     50         SplayNode* pool;
     51         SplayNode** s;
     52 
     53         LinkCutTree():pool(NULL), s(NULL) {        }
     54         LinkCutTree(int n):top(0) {
     55             pool = new SplayNode[(n + 1)];
     56             s = new SplayNode*[(n + 1)];
     57         }
     58 
     59         void rotate(SplayNode *p) {
     60             int d = (p->fa->ch[1] == p), d1 = (p->fa->fa) ? (p->fa->fa->ch[1] == p->fa) : (-1);
     61             SplayNode *fa = p->fa, *ffa = fa->fa, *ls = p->ch[d ^ 1];
     62             boolean sign = fa->isRoot();
     63             p->ch[d ^ 1] = fa, fa->fa = p;
     64             fa->ch[d] = ls, (ls) ? (ls->fa = fa) : (0);
     65             p->fa = ffa, (!sign && ~d1) ? (ffa->ch[d1] = p) : (0);
     66             fa->pushUp(), p->pushUp();
     67         }
     68 
     69         void splay(SplayNode* p) {
     70             SplayNode *np = p, *fp = p->fa;
     71             for (top = 0; s[++top] = np, !np->isRoot(); np = np->fa);
     72             for (; top; top--)
     73                 if (s[top]->rev)
     74                     s[top]->pushDown();
     75             for (; !p->isRoot(); rotate(p), fp = p->fa)
     76                 if (!fp->isRoot())
     77                     rotate(((fp->fa->ch[1] == fp) == (fp->ch[1] == p)) ? (fp) : (p));
     78         }
     79 
     80         void access(SplayNode *p) {
     81             SplayNode *q = NULL;
     82             while (p) {
     83                 splay(p);
     84                 p->vs += ((p->ch[1]) ? (p->ch[1]->ls) : (0));
     85                 p->vs -= ((q) ? (q->ls) : (0));
     86                 p->ch[1] = q;
     87                 p->pushUp();
     88                 q = p, p = p->fa;
     89             }
     90         }
     91 
     92         void mkroot(SplayNode *p) {
     93             access(p);
     94             splay(p);
     95             p->rev ^= 1;
     96         }
     97 
     98         void link(int u, int v) {
     99             SplayNode *a = pool + u, *b = pool + v;
    100             mkroot(b);
    101             access(a);
    102             splay(a);
    103             b->fa = a;
    104             a->vs += b->ls;
    105             a->pushUp();
    106         }
    107         
    108         SplayNode* findPre(SplayNode* p) {
    109             splay(p);
    110             p = p->ch[0];
    111             while (p) {
    112                 if (p->rev)    p->pushDown();
    113                 if (!p->ch[1])    break;
    114                 p = p->ch[1];
    115             }
    116             return p;
    117         }
    118         
    119         ll query(int u, int v) {
    120             SplayNode *p = pool + u, *q = pool + v;
    121             mkroot(p);
    122             access(q);
    123             splay(p);
    124             int s1 = q->vs + 1, s2 = p->ls; 
    125             return s1 * 1ll * (s2 - s1);    
    126         }
    127 }LinkCutTree;
    128 
    129 int n, m;
    130 LinkCutTree lct;
    131 
    132 inline void init() {
    133     scanf("%d%d", &n, &m);
    134     lct = LinkCutTree(n);
    135 }
    136 
    137 inline void solve() {
    138     char s[5];
    139     int u, v;
    140     while (m--) {
    141         scanf("%s%d%d", s, &u, &v);
    142         if (s[0] == 'A')
    143             lct.link(u, v);
    144         else
    145             printf(Auto"
    ", lct.query(u, v));
    146     }
    147 }
    148 
    149 int main() {
    150     init();
    151     solve();
    152     return 0;
    153 }
    View Code

    小结

      LCT的时间复杂度虽然只带一个log,但是算上常数没有两个log的树链剖分快,而且树链剖分好写好调,LCT细节繁杂,所以尽量挖掘题目性质,避免盲目使用LCT(比如有些题可以线段数分治或者离线树剖就可以过)。

      LCT维护链的常用套路:将一个点设为根,将另一个点access。

      提取LCT中链、子树信息的时候要考虑是否提取到的信息是否是想要的(比如我经常忘记先splay)

      LCT中splay操作时,最好先进行从上往下进行pushDown。我不知道为什么在旋转操作中pushDown会在一些题目蜜汁T掉。

    特别感谢

      ZJC

      Doggu

      MaxMercer

    参考资料

      neither_nor的讲课ppt

      《QTREE解法的一些研究》  杨哲

  • 相关阅读:
    OAuth 2 开发人员指南(Spring security oauth2)
    Android如何在ListView中嵌套ListView
    Android之ScrollView嵌套ListView冲突
    Android 去除EditText边框,添加下划线,
    Android日期时间选择器实现以及自定义大小
    验证Android用户输入日期
    Cygwin安装时,选择163的源后出错:Unable to get setup.ini from <http://mirrors.163.com/cygwin/>
    Windows平台下安装Hadoop
    如何在windows下安装cygwin
    连接Linux服务器:Win免费SSH客户端工具
  • 原文地址:https://www.cnblogs.com/yyf0309/p/link_cut_tree.html
Copyright © 2020-2023  润新知