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