[BJOI2014][BZOJ4530][洛谷P4219]大融合(LCT)

Solution

  • \(LCT\),对每个节点 \(u\) 维护两个信息:\(s[u], si[u]\)
  • \(sze[u]\) 为实子树的大小,即 \(splay\) 上的子树大小
  • \(s[u]=si[u]+sze[u]\)
  • \(si[u]\) :虚子树的大小之和,即所有虚儿子的 \(s\) 之和
  • 如果 \(u\) 是所在重链的根,那么 \(s[u]\)\(u\) 原树(即题目中所给的树结构)上的子树大小
  • 对于边 \((x,y)\),如果执行 \(split(x,y)\),那么 \((si[x]+1) * (si[y] + 1)\) 即答案
  • 考虑如何维护 \(s\)\(si\)
  • 显然初始所有 \(s[u] = 1,si[u]=0\)
  • 改变虚儿子会改变 \(si[u]\),改变实儿子和虚儿子都会改变 \(s[u]\)
  • 也就是说 \(rotate,access,link\) 都会改变至少一个信息
  • 对于 \(rotate\),和维护其它信息一样,旋转之后 \(upt/pushup\) 即可
  • 对于 \(access\),给出如下代码:
inline void access(int x)
{
    for (int y = 0; x; y = x, x = fa[x])
    {
        splay(x);
        si[x] += s[rc[x]];
        si[x] -= s[y];
        rc[x] = y;
        if (y) fa[y] = x;
    }
}
  • 可以看出,\(x\) 原来的实儿子变虚儿子,\(y\) 所在重链的根由虚儿子变实儿子,所以要修改 \(si[x]\),但是 \(s[x]\) 不用修改,因为两个儿子虚实互换,实儿子 \(+\) 虚儿子大小不会改变
  • 对于 \(link\),给出如下代码
inline void link(int x, int y)
{
    makeroot(x);
    access(y);
    splay(y);
    fa[x] = y;
    si[y] += s[x];
    upt(y);
}
  • 可以看出,\(y\) 多出来了一个实儿子 \(x\),所以要修改 \(si[y]\)\(s[y]\)
  • 重点是,要先 \(access(y), splay(y)\),因为 \(si[y]\)\(s[y]\) 的修改会导致 \(y\) 的祖先信息发生变化,所以让 \(y\) 和根合到一个 \(splay\) 中并将 \(y\) 转到根,即让 \(fa[y] = 0\),就没有祖先受到影响了
  • 现在问题来了,刚才的 \(access\) 中修改了 \(si\) ,是否也要考虑对祖先的影响呢?其实不用,观察这段代码可知,每次都 \(splay(x)\),也就是不用考虑对同一 \(splay\) 中的祖先的影响了。而且只修改 \(si[x]\),对不同 \(splay\) 中的祖先没有任何影响

Code

#include <bits/stdc++.h>

using namespace std;

template <class t>
inline void read(t & res)
{
    char ch;
    while (ch = getchar(), !isdigit(ch));
    res = ch ^ 48;
    while (ch = getchar(), isdigit(ch))
    res = res * 10 + (ch ^ 48);
}

const int e = 1e5 + 5;
int n, m, lc[e], s[e], si[e], rc[e], rev[e], fa[e], q[e];

inline bool which(int x)
{
    return rc[fa[x]] == x;
}

inline bool isroot(int x)
{
    return !fa[x] || (lc[fa[x]] != x && rc[fa[x]] != x);
}

inline void pushdown(int x)
{
    if (rev[x])
    {
        if (lc[x]) rev[lc[x]] ^= 1;
        if (rc[x]) rev[rc[x]] ^= 1;
        swap(lc[x], rc[x]);
        rev[x] = 0;
    }
}

inline void upt(int x)
{   
    s[x] = s[lc[x]] + s[rc[x]] + 1 + si[x];
}

inline void rotate(int x)
{
    int y = fa[x], z = fa[y];
    pushdown(y);
    pushdown(x);
    int b = (lc[y] == x ? rc[x] : lc[x]);
    if (z && !isroot(y)) (lc[z] == y ? lc[z] : rc[z]) = x;
    fa[x] = z;
    fa[y] = x;
    if (b) fa[b] = y;
    if (lc[y] == x)
    {
        rc[x] = y;
        lc[y] = b;
    }
    else
    {
        lc[x] = y;
        rc[y] = b;
    }
    upt(y);
    upt(x);
}

inline void splay(int x)
{
    int w;
    q[w = 1] = x;
    for (int y = x; !isroot(y); y = fa[y]) q[++w] = fa[y];
    for (int i = w; i >= 1; i--) pushdown(q[i]);
    while (!isroot(x))
    {
        if (!isroot(fa[x]))
        {
            if (which(x) == which(fa[x])) rotate(fa[x]);
            else rotate(x);
        }
        rotate(x);
    }
}

inline void access(int x)
{
    for (int y = 0; x; y = x, x = fa[x])
    {
        splay(x);
        si[x] += s[rc[x]];
        si[x] -= s[y];
        rc[x] = y;
        if (y) fa[y] = x;
    }
}

inline void makeroot(int x)
{
    access(x);
    splay(x);
    rev[x] ^= 1;
}

inline void link(int x, int y)
{
    makeroot(x);
    access(y);
    splay(y);
    fa[x] = y;
    si[y] += s[x];
    upt(y);
}

inline void split(int x, int y)
{
    makeroot(x);
    access(y);
    splay(y);
}

int main()
{
    int i, x, y;
    char ch;
    read(n); read(m);
    for (i = 1; i <= n; i++) s[i] = 1;
    while (m--)
    {
        while (ch = getchar(), !isalpha(ch));
        read(x);
        read(y);
        if (ch == 'A') link(x, y);
        else
        {
            split(x, y);
            printf("%lld\n", 1ll * (si[x] + 1) * (si[y] + 1));
        }
    }
    return 0;
}
posted @ 2020-01-15 15:24  花淇淋  阅读(98)  评论(0编辑  收藏  举报