给出一颗以 \(1\) 为根且有 \(n\) 个节点的树,一开始每个节点都是一颗棋子,一面白一面黑,白色的面朝上。
接下来就 \(q\) 次操作,操作分两种:
LCA
最深LCA
的编号(若当前没有黑棋子输出 \(0\))。对于 \(100 \%\) 的数据,\(1 ≤ n ≤ 800000\), \(1 ≤ q ≤ 800000\)。
思路应该算是比较好想的了。
用树状数组或线段树加树剖维护子树内黑色朝上的棋子的个数。
对于操作 \(0\),非常简单,不讲了。
对于操作 \(1\),每次类似于树剖跳链,不同的是一直跳父亲,判断当前跳到的节点的子树内是否有黑棋,有就直接输出。(这样做的理由是,一个节点与另一个节点的 LCA
只可能存在于两个节点中任意一个节点到根节点的简单路径上)
注意,本题数据过大,可能会爆栈,可以吸氧。
#include <bits/stdc++.h> #define _ (int)800000 + 5 using namespace std; int read() { int x = 0, f = 1; char c = getchar(); while (c < '0' || c > '9') { if (c == '-') f = -1; c = getchar(); } while (c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); } return x * f; } int n, m; int fa[_]; int a[_]; bool tag[_]; int tot, head[_], to[_ << 1], nxt[_ << 1]; int cnt_node, siz[_], dfn[_], hson[_]; void add(int u, int v) { to[++tot] = v; nxt[tot] = head[u]; head[u] = tot; } void dfs1(int u) { siz[u] = 1; for (int i = head[u]; i; i = nxt[i]) { int v = to[i]; if (siz[v]) continue; dfs1(v); siz[u] += siz[v]; if (siz[hson[u]] < siz[v]) hson[u] = v; } } void dfs2(int u) { dfn[u] = ++cnt_node; if (!hson[u]) return; dfs2(hson[u]); for (int i = head[u]; i; i = nxt[i]) { int v = to[i]; if (!dfn[v]) dfs2(v); } } void update(int x) { int y = 1; if (tag[x]) y = -1; tag[x] ^= 1; for (int i = x; i <= n; i += i & -i) a[i] += y; } int query(int x) { int res = 0; for (int i = x; i > 0; i -= i & -i) res += a[i]; return res; } bool Query(int x) { return query(dfn[x] + siz[x] - 1) - query(dfn[x] - 1); } int solve(int x) { while (x) { if (Query(x)) return x; x = fa[x]; } return 0; } signed main() { n = read(); m = read(); for (int i = 2; i <= n; ++i) { fa[i] = read(); add(fa[i], i); add(i, fa[i]); } dfs1(1); dfs2(1); for (int i = 1; i <= m; ++i) { int x; x = read(); if (x > 0) { update(dfn[x]); } else { printf("%d\n", solve(-x)); } } return 0; }