CF842E Nikita and game

本文介绍了一种解决树结构中直径端点问题的算法,通过将直径中点的移动转化为子树答案的更新,利用dfn+线段树的数据结构,实现了在(O(n log n))复杂度下高效查询和修改答案。关键在于理解直径中点的角色及其对子树的影响。
摘要由CSDN通过智能技术生成

一棵树初始只有一个编号为\(1\)的根结点。\(n(n\le3\times10^5)\)次操作,每次新增一个点作为\(p_i\)的子结点,询问更新后有多少点可以作为树直径的端点。


有一个显然的转化就是:有多少个点能作为直径端点 = 有多少点到直径中点距离等于直径长的一半。但直径中点可以在点上,也可以在某条边的中点。但两者本质是一样的,因为你可以往中点所在的边中间加一个点。所以下面只探讨在点上的情况。

假设当前的直径中点为\(rt\),那么\(rt\)会把树分割为若干个子树,假设当前加入的点为\(u\)并且在以\(v\)为根的子树中(被\(rt\)分割出的子树)。设直径长度的一半为\(len\),那么我们考虑三种情况,一种是\(u\)\(rt\)的距离\(<len\),此时不需要做任何事;如果\(=len\),那么就多了一个能当端点的点;如果\(>len\),那么先前\(v\)子树中所有的答案点都不能作为端点了,那么答案减去这些点的数量,同时让\(u\)成为新的答案点,并且此时直径长度就\(+1\)了,\(rt\)会相应地移动半条边。

所以我们需要做的就是查询并修改每个点子树内的答案点数量,很normal的dfn+线段树。

复杂度\(nlogn\)

#include<bits/stdc++.h>
#define rg register
#define il inline
#define cn const
#define gc getchar()
#define fp(i,a,b) for(rg int i=(a),ed=(b);i<=ed;++i)
#define fb(i,a,b) for(rg int i=(a),ed=(b);i>=ed;--i)
#define go(u) for(rg int i=head[u];~i;i=e[i].nxt)
using namespace std;
typedef cn int cint;
typedef long long LL;
il int rd(){
	rg int x(0),f(1); rg char c(gc);
	while(c<'0'||'9'<c){if(c=='-')f=-1;c=gc;}
	while('0'<=c&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=gc;
	return x*f;
}
cint maxn=3e5+10;
int n,p[maxn],fa[maxn][19],dep[maxn],dfn[maxn],lst[maxn],tot;
struct edge{int to,nxt;}e[maxn];
int head[maxn],k;
il void add(cint &u,cint &v){e[k]=(edge){v,head[u]},head[u]=k++;}
void dfs(int u){
	dfn[u]=++tot;
	fp(i,1,18)fa[u][i]=fa[fa[u][i-1]][i-1];
	go(u)dep[e[i].to]=dep[u]+1,dfs(e[i].to);
	lst[u]=tot;
}

int cnt[maxn<<2],tg[maxn<<2];
void ins(int d,int l,int r,cint &des){
	if(l==r)return ++cnt[d],void();
	if(tg[d])tg[d]=0,cnt[d<<1]=cnt[d<<1|1]=0,tg[d<<1]=tg[d<<1|1]=1;
	int md=l+r>>1;
	if(des<=md)ins(d<<1,l,md,des);
	else ins(d<<1|1,md+1,r,des);
	cnt[d]=cnt[d<<1]+cnt[d<<1|1];
}
void clr(int d,int l,int r,cint &dl,cint &dr){
	if(dl<=l&&r<=dr)return tg[d]=1,cnt[d]=0,void();
	if(tg[d])tg[d]=0,cnt[d<<1]=cnt[d<<1|1]=0,tg[d<<1]=tg[d<<1|1]=1;
	int md=l+r>>1;
	if(dl<=md)clr(d<<1,l,md,dl,dr);
	if(dr>md)clr(d<<1|1,md+1,r,dl,dr);
	cnt[d]=cnt[d<<1]+cnt[d<<1|1];
}
int qry(int d,int l,int r,cint &dl,cint &dr){
	if(dl<=l&&r<=dr)return cnt[d];
	if(tg[d])tg[d]=0,cnt[d<<1]=cnt[d<<1|1]=0,tg[d<<1]=tg[d<<1|1]=1;
	int md=l+r>>1,ans=0;
	if(dl<=md)ans=qry(d<<1,l,md,dl,dr);
	if(dr>md)ans+=qry(d<<1|1,md+1,r,dl,dr);
	return ans;
}

il int getlca(int u,int v){
	if(dep[u]>dep[v])swap(u,v);
	fb(i,18,0)if(dep[fa[v][i]]>=dep[u])v=fa[v][i];
	if(u==v)return u;
	fb(i,18,0)if(fa[u][i]!=fa[v][i])u=fa[u][i],v=fa[v][i];
	return fa[u][0];
}

int len,type,rt=1,rt1,rt2;
il int slv1(cint &u){
	rg int lca=getlca(u,rt),dis=dep[u]+dep[rt]-dep[lca]*2;
	if(dis==len)ins(1,1,n,dfn[u]);
	else if(dis>len){
		if(dfn[rt]<=dfn[u]&&dfn[u]<=lst[rt]){
			rg int son=u;
			fb(i,18,0)if(dep[fa[son][i]]>dep[rt])son=fa[son][i];
			clr(1,1,n,dfn[son],lst[son]),ins(1,1,n,dfn[u]);
			type=1,rt1=rt,rt2=son;
		}else{
			if(dfn[rt]>1)clr(1,1,n,1,dfn[rt]-1);
			if(lst[rt]<n)clr(1,1,n,lst[rt]+1,n);
			ins(1,1,n,dfn[u]);
			type=1,rt1=fa[rt][0],rt2=rt;
		}
	}
	return cnt[1];
}
il int slv2(cint &u){//当然你也可以把边拆成点
	rg int lca,dis;
	if(dfn[rt2]<=dfn[u]&&dfn[u]<=lst[rt2]){
		lca=getlca(u,rt2);
		dis=dep[u]+dep[rt2]-dep[lca]*2;
	}else{
		lca=getlca(u,rt1);
		dis=dep[u]+dep[rt1]-dep[lca]*2;
	}
	if(dis==len)ins(1,1,n,dfn[u]);
	else if(dis>len){
		if(dfn[rt2]<=dfn[u]&&dfn[u]<=lst[rt2]){
			clr(1,1,n,dfn[rt2],lst[rt2]),ins(1,1,n,dfn[u]);
			type=0,rt=rt2,++len;
		}else{
			if(dfn[rt2]>1)clr(1,1,n,1,dfn[rt2]-1);
			if(lst[rt2]<n)clr(1,1,n,lst[rt2]+1,n);
			ins(1,1,n,dfn[u]);
			type=0,rt=rt1,++len;
		}
	}
	return cnt[1];
}
int main(){
	memset(head,-1,sizeof head);
	n=rd()+1;
	fp(i,2,n)p[i]=fa[i][0]=rd(),add(p[i],i);
	dfs(1),ins(1,1,n,1);
	fp(i,2,n){
		if(type==0)printf("%d\n",slv1(i));
		else printf("%d\n",slv2(i));
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值