1. 程式人生 > 其它 >洛谷 P4332 [SHOI2014]三叉神經樹 題解

洛谷 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;
}