【51nod1576】Tree and Permutation(树的重心)(DFS序)(树状数组)

传送门


解析:

有一点想法,考虑每条边被经过的次数,假设这条边将树给分为两个大小为 x , y x,y x,y的联通块,则这条边的最大贡献为 min ⁡ ( x , y ) ∗ 2 \min(x,y)*2 min(x,y)2,显然我们考虑给每个点尽量选择在另一边的点就行了。

那么能不能让每条边的贡献达到可能的最大值呢?是可以的。

发现我们算的 min ⁡ ( x , y ) \min(x,y) min(x,y)实际上就是以重心为根的时候,改变连向的儿子的子树大小。

考虑如下构造:
选取重心为根,可以发现每个 p [ i ] p[i] p[i]都一定可以选择与 i i i不在同一个子树中的点,因为每一个子树 s i z < = t o t a l / 2 siz<=total/2 siz<=total/2

那么每条边都会被它子树中的点的路径穿过,贡献被最大化。

发现总的答案就是所有点到重心的距离之和。

我们需要动态维护重心,可以上LCT。

不过直接用DFS序+树状数组维护所有子树中被激活了的点的个数就行了,显然加入每个点的时候,重心平移的距离最多只有 1 1 1,而且是向着加入的点的方向平移,算一算当前大小就行了。


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define gc get_char
#define cs const

namespace IO{
	inline char get_char(){
		static cs int Rlen=1<<20|1;
		static char buf[Rlen],*p1,*p2;
		return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
	}
	
	inline int getint(){
		re char c;
		while(!isdigit(c=gc()));re int num=c^48;
		while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48);
		return num;
	}
}
using namespace IO;

using std::cout;
using std::cerr;

cs int N=1e5+5;
int n;
std::vector<int> e[N];
inline void addedge(int u,int v){
	e[u].push_back(v);
	e[v].push_back(u);
}

int fa[N],dep[N],top[N],siz[N],son[N];
int in[N],out[N],dfs_clock;

void dfs1(int u){
	siz[u]=1;
	in[u]=++dfs_clock;
	for(re int v:e[u])if(v!=fa[u]){
		fa[v]=u;
		dep[v]=dep[u]+1;
		dfs1(v);
		siz[u]+=siz[v];
		if(siz[v]>siz[son[u]])son[u]=v;
	}
	out[u]=dfs_clock;
}
void dfs2(int u){
	if(son[u]){
		top[son[u]]=top[u];
		dfs2(son[u]);
	}
	else return ;
	for(re int v:e[u])if(v!=fa[u]&&v!=son[u]){
		top[v]=v;
		dfs2(v);
	}
}
inline int LCA(int u,int v){
	while(top[u]^top[v])dep[top[u]]<dep[top[v]]?v=fa[top[v]]:u=fa[top[u]];
	return dep[u]<dep[v]?u:v;
}
inline int jump(int u,int to){
	int res;
	while(top[u]^top[to])u=fa[res=top[u]];
	return u==to?res:son[to];
}

int bit[N];
inline void add(int pos){
	for(;pos;pos^=pos&-pos)++bit[pos];
}
inline int query(int pos){int res=0;
	for(;pos<=n;pos+=pos&-pos)res+=bit[pos];
	return res;
}
inline int query(int l,int r){
	return query(l)-query(r+1);
}

int G=1;
ll ans;
signed main(){
	std::ios::sync_with_stdio(false);cout.tie(NULL);
	n=getint()+1;
	for(int re i=2;i<=n;++i)addedge(i,getint());
	dfs1(1),top[1]=1,dfs2(1);
	add(in[1]);
	for(int re i=2;i<=n;++i){
		add(in[i]);
		int lca=LCA(G,i);
		ans+=dep[G]+dep[i]-dep[lca]*2;
		if(lca==G){
			int u=jump(i,G),siz=query(in[u],out[u]);
			if(siz>i-siz)G=u,ans-=siz*2-i;
		}
		else {
			int u=fa[G],siz=query(in[G],out[G]);
			if(i-siz>siz)G=u,ans-=i-siz*2;
		}
		cout<<ans<<"\n";
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值