初见安~这里是传送门: CodeForces 842E
题解
题意是给一棵树,每次增加一个点,问有多少点可以作为直径的端点。
考虑加入一个点后树的直径的变化。如果没有改变树的直径的长度,那么有两种情况:不影响直径 和 成为直径一端的点。如果能成为一个直径一端的点,当且仅当与直径另一端的某点距离长度为直径长。
根据树的直径的性质,若有多条直径,也必然有唯一连续的一段交集(至少一个点)。
所以:维护直径两端点的集合记为S和T,加入当前点后计算与S中任意一点和T中任意一点的距离,若与其中一个距离长度等于直径,则加入另一个集合。
那如果长度大于直径呢?假如当前点i与S的距离大于直径,那么i就会取代集合T。但我们可以发现并不一定是完全取代,有可能原本T中的点可以加入S继续作为直径一端的点。(构造形如三等分一个圆的三条半径的树?)所以对应情况讨论一下就行了。
关于复杂度与正确性:
1、若一个点被一个集合扔出去了(没有进入另一端的集合),那么一定不会再回来。(证明可能会假掉,如果是希望评论指正QvQ)因为出现这种情况必然出现直径被更新,而当前节点能更新直径长度则一定是接在了其中一个端点集合中的一个点上,这样一来这一端的其他点依旧留在这一端是一定不优的,而舍弃的部分是一定打不过另一端的集合的点的,可以舍弃。
2、S和T集合互相扔点,每个点也最多被扔一次。假设当前点i被从S扔到了T,若i要被从T扔回S,而这次新加入的点在T集合内,能被扔到另一个集合的必要条件是到S和T原本的距离相同,这就矛盾了。
所以每个点至多被操作两次,整体复杂度。
上代码——
#include<algorithm>
#include<iostream>
#include<cstring>
#include<vector>
#include<cstdio>
#include<vector>
#include<cmath>
#include<queue>
#define maxn 300005
using namespace std;
typedef long long ll;
int read() {
int x = 0, f = 1, ch = getchar();
while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
return x * f;
}
int n, fa[maxn][22], dep[maxn];
vector<int> s1, s2;
void add(int u, int v) {//因为要求两点间距离,所以搞了一个倍增……
fa[v][0] = u; dep[v] = dep[u] + 1;
for(int i = 1; i <= 20; i++) fa[v][i] = fa[fa[v][i - 1]][i - 1];
}
int dis(int u, int v) {
register int tu = u, tv = v, lca;
if(dep[u] > dep[v]) swap(u, v);
for(int i = 20; i >= 0; i--) if(dep[fa[v][i]] >= dep[u]) v = fa[v][i];
if(u == v) return dep[tu] + dep[tv] - dep[u] - dep[v];
for(int i = 20; i >= 0; i--) if(fa[u][i] != fa[v][i]) u = fa[u][i], v = fa[v][i];
lca = fa[u][0];
return dep[tu] + dep[tv] - dep[lca] - dep[lca];
}
signed main() {
n = read() + 1;
s1.push_back(1);
register int max_d = 0;
for(int u, v = 2; v <= n; v++) {
u = read(); add(u, v);
register int d1 = 0, d2 = 0;
if(s1.size()) d1 = dis(v, s1[0]);
if(s2.size()) d2 = dis(v, s2[0]);
if(max(d1, d2) > max_d) {//更新直径
max_d = max(d1, d2);
if(d1 == max_d) {
for(int i = 0; i < s2.size(); i++) {
register int x = s2[i];
if(dis(v, x) == max_d) s1.push_back(x);//扔到对面集合
}
s2.clear();
} else {
for(int i = 0; i < s1.size(); i++) {
register int x = s1[i];
if(dis(v, x) == max_d) s2.push_back(x);
}
s1.clear();
}
}
if(d1 == max_d) s2.push_back(v);//判断当前点加入哪边集合
else if(d2 == max_d) s1.push_back(v);
printf("%d\n", s1.size() + s2.size());
}
return 0;
}
迎评:)
——End——