题意
给一颗初始大小为\(1\)的有根树,每次可以为该树添加一个叶子结点
定义对树的一次变换为交换该树中互不为祖宗的两个结点的子树
在每次添加一个叶子结点后,请输出当前树经过至多一次变换后的直径长度
解法
如果不进行交换,答案显然是原树的直径
进行一次交换后,答案即为原树的直径\(+\)离直径最远的点到直径的距离\(-1\)(减一是因为在换子树的过程中,有一条边被重合掉了)
由于是有根树,我们考虑用深度\(+\)LCA的经典套路维护两点之间的距离
这样,每添加一个节点,我们就\(\log n\)更新其倍增数组
在添加一个节点之后,树的直径与离树的直径最远的点可能会改变,因此我们维护三个点即可:直径的两个端点,离直径最远的点
上面的这个方法是基于求得的答案是单调不降这个事实的,而这个是很好证明的:直径与离直径最远的点均是单调不降的,所以最后求得的答案也是单调不降的
代码
#include <cstdio>
#include <iostream>
using namespace std;
const int N = 2e5 + 10;
int read();
int id, n;
int f[N][25], dep[N];
int len, u, v, w;
inline int min(int x, int y) { return x < y ? x : y; }
inline int max(int x, int y) { return x > y ? x : y; }
int LCA(int x, int y) {
if (dep[x] < dep[y]) swap(x, y);
for (int i = 20; i >= 0; --i)
if (dep[f[x][i]] >= dep[y]) x = f[x][i];
if (x == y) return y;
for (int i = 20; i >= 0; --i)
if (f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
return f[x][0];
}
inline int get(int x, int y) {
return dep[x] + dep[y] - 2 * dep[LCA(x, y)];
}
inline int dis(int x) {
int lca = LCA(u, v);
if (LCA(lca, x) != lca)
return get(x, lca);
else
return min(get(x, LCA(x, u)), get(x, LCA(x, v)));
}
void update(int x) {
int l = get(x, u), r = get(x, v);
if (l < r)
swap(u, v), swap(l, r);
if (l > len)
len = l, swap(v, x);
if (dis(x) > dis(w)) swap(w, x);
}
int main() {
id = read(), n = read();
int lstans = 0;
u = v = w = 1;
dep[1] = 2;
for (int i = 2; i <= n; ++i) {
int x = read() ^ lstans;
dep[i] = dep[x] + 1, f[i][0] = x;
for (int j = 1; j <= 20; ++j) f[i][j] = f[f[i][j - 1]][j - 1];
update(i);
printf("%d\n", lstans = len + max(dis(w) - 1, 0));
}
return 0;
}
int read() {
int x = 0, c = getchar();
while (c < '0' || c > '9') c = getchar();
while (c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x;
}