题目大意
给出一颗以 \(1\) 为根且有 \(n\) 个节点的树,一开始每个节点都是一颗棋子,一面白一面黑,白色的面朝上。
接下来就 \(q\) 次操作,操作分两种:
- \(0\) 操作,将一颗棋子翻转。
- \(1\) 操作,询问一颗棋子与所有面朝上为黑色的棋子的
LCA
最深LCA
的编号(若当前没有黑棋子输出 \(0\))。
对于 \(100 \%\) 的数据,\(1 ≤ n ≤ 800000\), \(1 ≤ q ≤ 800000\)。
解题思路
思路应该算是比较好想的了。
用树状数组或线段树加树剖维护子树内黑色朝上的棋子的个数。
对于操作 \(0\),非常简单,不讲了。
对于操作 \(1\),每次类似于树剖跳链,不同的是一直跳父亲,判断当前跳到的节点的子树内是否有黑棋,有就直接输出。(这样做的理由是,一个节点与另一个节点的 LCA
只可能存在于两个节点中任意一个节点到根节点的简单路径上)
注意,本题数据过大,可能会爆栈,可以吸氧。
AC CODE
#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;
}