题意
传送门 Codeforces 208E Blood Cousins
题解
倍增 + dsu on tree
查询节点的第 p p p 堂兄弟节点的数量,由于它们的第 p p p 父节点相同,那么问题可以转化为父节点的子树信息统计。
具体而言,倍增法
O
(
N
log
N
)
O(N\log N)
O(NlogN) 预处理出父节点信息,在各个查询对应的第
p
p
p 父节点处记录需要进行的查询。
d
s
u
o
n
t
r
e
e
dsu\ on\ tree
dsu on tree 在
O
(
N
log
N
)
O(N\log N)
O(NlogN) 内维护以
x
x
x 为根节点的子树中深度为
d
d
d 的节点数目
c
n
t
[
d
]
cnt[d]
cnt[d],那么查询
(
v
,
p
)
,
p
−
t
h
a
n
c
e
s
t
o
r
=
x
(v,p),p-th\ ancestor=x
(v,p),p−th ancestor=x 对应答案为
c
n
t
[
d
e
p
[
x
]
+
p
]
−
1
cnt[dep[x]+p]-1
cnt[dep[x]+p]−1 需要注意的是,数据中可能出现森林,而且要特殊处理
d
e
p
[
v
]
<
p
dep[v]<p
dep[v]<p 的情况。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 100005, maxlg = 17;
struct node
{
int d, k;
};
int N, M, lg[maxn], dep[maxn], fa[maxn][maxlg], sz[maxn];
int cnt[maxn], res[maxn];
vector<int> G[maxn];
vector<node> Q[maxn];
void pdfs(int x, int f, int d)
{
fa[x][0] = f, dep[x] = d, sz[x] = 1;
for (int k = 1; k <= lg[dep[x]]; ++k)
fa[x][k] = fa[fa[x][k - 1]][k - 1];
for (auto &y : G[x])
if (y != f)
pdfs(y, x, d + 1), sz[x] += sz[y];
}
int get_f(int x, int d)
{
while (d)
x = fa[x][lg[d]], d -= 1 << lg[d];
return x;
}
bool hv[maxn];
void add(int x, int f, int d)
{
cnt[dep[x]] += d;
for (auto &y : G[x])
if (y != f && !hv[y])
add(y, x, d);
}
void dfs(int x, int f, int keep)
{
int mx = -1, hs = -1;
for (auto &y : G[x])
if (y != f && sz[y] > mx)
mx = sz[y], hs = y;
for (auto &y : G[x])
if (y != f && y != hs)
dfs(y, x, 0);
if (hs != -1)
dfs(hs, x, 1), hv[hs] = 1;
add(x, f, 1);
for (auto &q : Q[x])
res[q.k] = cnt[dep[x] + q.d] - 1;
if (hs != -1)
hv[hs] = 0;
if (!keep)
add(x, f, -1);
}
int main()
{
scanf("%d", &N);
lg[0] = -1;
for (int i = 1; i < N; ++i)
lg[i] = lg[i >> 1] + 1;
vector<int> rt;
for (int i = 0, f; i < N; ++i)
{
scanf("%d", &f), --f;
if (f != -1)
G[f].push_back(i);
else
rt.push_back(i);
}
for (auto &x : rt)
pdfs(x, -1, 0);
scanf("%d", &M);
for (int i = 0, x, d; i < M; ++i)
{
scanf("%d%d", &x, &d), --x;
int f = -1;
if (dep[x] < d)
res[i] = 0;
else
f = get_f(x, d), Q[f].push_back(node{d, i});
}
for (auto &x : rt)
dfs(x, -1, 0);
for (int i = 0; i < M; ++i)
printf("%d%c", res[i], i == M - 1 ? '\n' : ' ');
return 0;
}
倍增 + DFS
由于深度为 d d d 的节点数目 c n t [ d ] cnt[d] cnt[d] 具备可差分性,可以使用 D F S DFS DFS 简单求解查询。具体而言,在遍历以 x x x 为根的子树前,在节点 x x x 记录 c n t [ d ] cnt[d] cnt[d],从 x x x 回溯前,得到新的 c n t ′ [ d ] cnt^\prime[d] cnt′[d],差值 c n t ′ [ d ] − c n t [ d ] cnt^\prime[d]-cnt[d] cnt′[d]−cnt[d] 即子树中深度为 d d d 的节点数量。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 100005, maxlg = 17;
struct node
{
int d, k, pre;
};
int N, M, lg[maxn], dep[maxn], fa[maxn][maxlg];
int cnt[maxn], res[maxn];
vector<int> G[maxn];
vector<node> Q[maxn];
void pdfs(int x, int f, int d)
{
fa[x][0] = f, dep[x] = d;
for (int k = 1; k <= lg[dep[x]]; ++k)
fa[x][k] = fa[fa[x][k - 1]][k - 1];
for (auto &y : G[x])
if (y != f)
pdfs(y, x, d + 1);
}
int get_f(int x, int d)
{
while (d)
x = fa[x][lg[d]], d -= 1 << lg[d];
return x;
}
void dfs(int x, int f)
{
int d;
for (auto &q : Q[x])
d = dep[x] + q.d, q.pre = cnt[d];
++cnt[dep[x]];
for (auto &y : G[x])
if (y != f)
dfs(y, x);
for (auto &q : Q[x])
d = dep[x] + q.d, res[q.k] = cnt[d] - q.pre - 1;
}
int main()
{
scanf("%d", &N);
lg[0] = -1;
for (int i = 1; i < N; ++i)
lg[i] = lg[i >> 1] + 1;
vector<int> rt;
for (int i = 0, f; i < N; ++i)
{
scanf("%d", &f), --f;
if (f != -1)
G[f].push_back(i);
else
rt.push_back(i);
}
for (auto &x : rt)
pdfs(x, -1, 0);
scanf("%d", &M);
for (int i = 0, x, d; i < M; ++i)
{
scanf("%d%d", &x, &d), --x;
int f = -1;
if (dep[x] < d)
res[i] = 0;
else
f = get_f(x, d), Q[f].push_back(node{d, i, 0});
}
for (auto &x : rt)
dfs(x, -1);
for (int i = 0; i < M; ++i)
printf("%d%c", res[i], i == M - 1 ? '\n' : ' ');
return 0;
}