洛谷 P4332 [SHOI2014]三叉神经树 题解

一、题目:

洛谷原题

二、思路:

这道题怎么说呢?只能说有点意思,让我第一次见识了LCT怎么应用。

首先一个非常明显的性质,就是比如我现在修改了某个叶子结点,记为 \(leaf\),那么因此而状态发生改变的点一定是从 \(leaf\) 向上的连续区间。所以我们自然而然能想到两种数据结构,一种是树链剖分,另一种就是LCT,因为这两种都可以较好地维护树链的信息。

这题用LCT怎么做呢?难点就在于找出每次的连续区间的终点在哪?当然,由于连续区间的性质,我们立刻就能想到二分答案。时间复杂度 \(O(n\log^2 n)\)。

其实,我们还可以通过维护一些信息来降低一下时间复杂度。具体来说,我们每次打通 \(leaf\) 到根的路径,即 \(\mathbb{access}(leaf)\)。这样一来,\(leaf\) 到根的所有节点就都在一棵 Splay 中了。我们在这棵 Splay 的每个节点中维护两个信息:\(id[x,1]\) 和 \(id[x,2]\),分别代表Splay中 \(x\) 子树的、原树中最深的、\(sum\) 不为 \(1\) 或 \(2\) 的节点编号。

具体怎么维护呢?请配合注释看代码。

三、代码:

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;
#define FILEIN(s) freopen(s".in", "r", stdin);
#define FILEOUT(s) freopen(s".out", "w", stdout)
#define mem(s, v) memset(s, v, sizeof s)

inline int read(void) {
    int x = 0, f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
    while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
    return f * x;
}

const int maxn = 500005 * 3;

int n, m, son[maxn][2], id[maxn][3], fa[maxn], add[maxn], val[maxn], sum[maxn], tot;
int head[maxn];

struct Edge {
    int y, next;
    Edge() {}
    Edge(int _y, int _next) : y(_y), next(_next) {}
}e[maxn << 1];

inline void connect(int x, int y) {
    e[++ tot] = Edge(y, head[x]);
    head[x] = tot;
}

inline void update(int o) { // 由于Splay结点的中序遍历代表原树的一条从上到下的路径,因此我们必须先看右儿子,再看它自己,最后看它的左儿子。
    id[o][1] = id[son[o][1]][1];
    id[o][2] = id[son[o][1]][2];
    if (!id[o][1]) {
        if (sum[o] != 1) id[o][1] = o;
        else id[o][1] = id[son[o][0]][1];
    }
    if (!id[o][2]) {
        if (sum[o] != 2) id[o][2] = o;
        else id[o][2] = id[son[o][0]][2];
    }
}

inline void change(int o, int x) {
    sum[o] += x; val[o] = sum[o] > 1;
    swap(id[o][1], id[o][2]); // 修改操作一定只对sum值全部为1或2的区间进行,因此我们只需交换id即可。
    add[o] += x;
}

inline void pushdown(int o) {
    if (add[o]) {
        if (son[o][0]) change(son[o][0], add[o]);
        if (son[o][1]) change(son[o][1], add[o]);
        add[o] = 0;
    }
}

inline bool isroot(int x) {
    return son[fa[x]][0] != x && son[fa[x]][1] != x;
}

inline int get(int x) { return son[fa[x]][1] == x; }

inline void rotate(int x) {
    int y = fa[x], z = fa[y], k = get(x);
    pushdown(y); pushdown(x);
    if (!isroot(y)) son[z][son[z][1] == y] = x;
    son[y][k] = son[x][k ^ 1]; fa[son[y][k]] = y; fa[y] = x;
    son[x][k ^ 1] = y; fa[x] = z;
    update(y); update(x);
}

void correct(int x) {
    if (!isroot(x)) correct(fa[x]);
    pushdown(x);
}

inline void splay(int x) {
    correct(x);
    for (int f = fa[x]; !isroot(x); rotate(x), f = fa[x])
        if (!isroot(f))
            rotate(get(x) == get(f) ? f : x);
}

inline void access(int x) {
    int z = x;
    for (int y = 0; x; y = x, x = fa[x]) {
        splay(x);
        son[x][1] = y;
        update(x);
    }
    splay(z);
}

void dfs(int x, int fa) {
    sum[x] = 0;
    for (int i = head[x], y; i; i = e[i].next) {
        y = e[i].y;
        if (y == fa) continue;
        dfs(y, x);
        sum[x] += val[y];
    }
    if (x <= n) val[x] = sum[x] > 1;
}

int main() {
    n = read();
    int x;
    for (int i = 1; i <= n; ++ i) {
        for (int j = 1; j <= 3; ++ j) {
            x = read(); fa[x] = i;
            connect(x, fa[x]); connect(fa[x], x); 
        }
    }
    for (int i = n + 1; i <= n * 3 + 1; ++ i) val[i] = read();
    dfs(1, 0);
    int ans = val[1];
    m = read(); 
    while (m --) {
        int leaf = read(), x = fa[leaf];
        int addtag = val[leaf] ? -1 : 1;
        access(x); 
        int w = id[x][val[leaf] ? 2 : 1];
        if (w) {
            splay(w);
            change(son[w][1], addtag);
            sum[w] += addtag; val[w] = sum[w] > 1; update(w); // 由于w的儿子的id发生了变化,注意要update一下,当然换成splay(w)也是可以的。
        }
        else ans ^= 1, change(x, addtag); // 特殊判断w不存在的情况。
        val[leaf] ^= 1;
        printf("%d\n", ans);
    }
    return 0;
}
上一篇:从css 3d说到空间坐标轴


下一篇:有趣的css—简单的下雨效果2.0版