洛谷传送门
BZOJ传送门
题目大意
给你一棵有 n n n个节点、 1 1 1号节点为根的无根树, q q q组询问, 每次询问一个点 x x x的子树内的重心是哪一个点。
输入输出格式
输入格式
第一行两个正整数 n , q n,q n,q。
第二行 n − 1 n-1 n−1个正整数 f i f_i fi, 表示 i + 1 i+1 i+1号点与 i i i号点有边。
以下 q q q行, 每行一个正整数 x x x, 表示一组询问。
输出格式
共 q q q行, 每行一个正整数, 表示对应询问的答案。
输入输出样例
输入样例#1:
7 4
1 1 3 3 5 3
1
2
3
5
输出样例#1:
3
2
3
6
解题分析
直接大力用 s e t set set维护每个子树中的点, 启发式合并即可。
查询 x x x时找到其 s i z ≥ ⌈ s i z x 2 ⌉ siz\ge \lceil\frac{siz_x}{2}\rceil siz≥⌈2sizx⌉的最小的一个子树即可。
当然也可以直接 O ( n ) O(n) O(n)做, 具体而言直接从某个 s i z > ⌈ s i z x 2 ⌉ siz>\lceil\frac{siz_x}{2}\rceil siz>⌈2sizx⌉的子树的重心开始向上枚举。 如果不存在这样的点那么重心就是当前点。(太懒了不写)
代码如下:
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <cstdlib>
#include <cctype>
#include <vector>
#include <set>
#define R register
#define IN inline
#define W while
#define gc getchar()
#define MX 300500
template <class T>
IN void in(T &x)
{
x = 0; R char c = gc;
for (; !isdigit(c); c = gc);
for (; isdigit(c); c = gc)
x = (x << 1) + (x << 3) + c - 48;
}
int n, cnt, q;
int ans[MX], siz[MX];
struct INFO {int siz, id;};
IN bool operator < (const INFO &x, const INFO &y)
{return x.siz < y.siz;}
std::multiset <INFO> st[MX];
std::vector <int> nex[MX], que[MX];
IN void add(R int from, R int to) {nex[from].push_back(to);}
void DFS(R int now, R int fa)
{
siz[now] = 1; R int foo, bar;
for (auto i : nex[now])
{
if (i == fa) continue;
DFS(i, now);
siz[now] += siz[i];
foo = st[now].size(), bar = st[i].size();
if (foo < bar)
{
for (auto j : st[now]) st[i].insert(j);
st[now].clear(); st[now].swap(st[i]);
}
else
{
for (auto j : st[i]) st[now].insert(j);
st[i].clear();
}
}
int tar = (siz[now] + 1) >> 1, res;
auto i = st[now].lower_bound({tar, 0});
if (i == st[now].end()) res = now;
else res = i -> id;
for (auto i : que[now]) ans[i] = res;
st[now].insert({siz[now], now});
}
int main(void)
{
in(n), in(q); int foo;
for (R int i = 2; i <= n; ++i) in(foo), add(i, foo), add(foo, i);
for (R int i = 1; i <= q; ++i) in(foo), que[foo].push_back(i);
DFS(1, 0);
for (R int i = 1; i <= q; ++i) printf("%d\n", ans[i]);
}