CF842E Nikita and game

初见安~这里是传送门: 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原本的距离相同,这就矛盾了。

所以每个点至多被操作两次,整体复杂度O(n)

上代码——

#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——

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值