Atcoder agc029E

先考虑一个点的总体访问顺序。我们发现一个点的子树内部可以划分为若干个根节点单调递增的连通块,满足连通块内部编号均小于根。于是我们访问的时候会依次访问这些连通块。对于两棵子树,我们会把连通块按根的编号归并起来访问,加入一个根节点会将根的编号最小的几个连通块与它合并。可以用线段树合并简单支持维护这些连通块,并记录它们的大小。
再考虑计算 c i c_i ci。对于点 i i i,它到 1 1 1号点路径上的所有节点均需访问。删除这部分节点后,我们得到若干个连通块,同样划分为根节点单调递增的子连通块。简单分析可以知道一个根为 x x x的连通块被访问当且仅当存在 y > x y>x y>x满足 y y y x x x 1 1 1的路径与 i i i 1 1 1的路径的交上,且不能为交最底下的节点。利用预处理的线段树同样容易计算。
时间复杂度 O ( N log ⁡ N ) \mathcal O(N\log N) O(NlogN)

#include <bits/stdc++.h>

using namespace std;

namespace SGT {

const int Maxn=20000000;

int ch[Maxn][2],sumv[Maxn],tot;

inline int cpynode(int o) {
  tot++;
  ch[tot][0]=ch[o][0];ch[tot][1]=ch[o][1];
  sumv[tot]=sumv[o];
  return tot;
}

int update(int l,int r,int o,int p,int q) {
  o=cpynode(o);
  sumv[o]+=q;
  if (l==r) return o;
  else {
  	int m=((l+r)>>1);
  	if (m>=p) ch[o][0]=update(l,m,ch[o][0],p,q);
  	else ch[o][1]=update(m+1,r,ch[o][1],p,q);
  	return o;
  }
}

int erase(int l,int r,int o,int p) {
  if (r<=p) return 0;
  else {
  	o=cpynode(o);
  	int m=((l+r)>>1);
  	if (m>=p) ch[o][0]=erase(l,m,ch[o][0],p);
  	else {
  		ch[o][0]=0;
  		ch[o][1]=erase(m+1,r,ch[o][1],p);
	  }
	sumv[o]=sumv[ch[o][0]]+sumv[ch[o][1]];
	return o;
  }
}

int merge(int l,int r,int x,int y) {
  if (!x) return y;
  if (!y) return x;
  int o=++tot,m=((l+r)>>1);
  ch[o][0]=merge(l,m,ch[x][0],ch[y][0]);
  ch[o][1]=merge(m+1,r,ch[x][1],ch[y][1]);
  sumv[o]=sumv[ch[o][0]]+sumv[ch[o][1]];
  return o;
}

int query(int l,int r,int o,int p) {
  if (!o) return 0;
  if (l==r) return sumv[o];
  else {
  	int m=((l+r)>>1);
  	if (m>=p) return query(l,m,ch[o][0],p);
  	else return sumv[ch[o][0]]+query(m+1,r,ch[o][1],p);
  }
}

}

vector <int> e[200005];
int n;

int root[200005];

void dfs1(int x,int fa) {
  for(int i=0;i<e[x].size();i++)
    if (e[x][i]!=fa) {
    	int u=e[x][i];
    	dfs1(u,x);
    	root[x]=SGT::merge(1,n,root[x],root[u]);
	}
  int v=SGT::query(1,n,root[x],x)+1;
  root[x]=SGT::erase(1,n,root[x],x);
  root[x]=SGT::update(1,n,root[x],x,v);
}

int maxn[200005],ans[200005];

void dfs2(int x,int fa) {
  if (x>1) {
  	ans[x]=ans[fa]+1;
  	if (fa>1) ans[x]-=SGT::query(1,n,root[x],maxn[fa]);
  	for(int i=0;i<e[x].size();i++)
  	  if (e[x][i]!=fa) {
  	  	  int u=e[x][i];
  	  	  ans[x]+=SGT::query(1,n,root[u],maxn[x]);
		}
  }
  for(int i=0;i<e[x].size();i++)
    if (e[x][i]!=fa) {
    	int u=e[x][i];
    	maxn[u]=max(maxn[x],x);
    	dfs2(u,x);
	}
}

int main() {
  scanf("%d",&n);
  for(int i=1;i<n;i++) {
  	int x,y;
  	scanf("%d%d",&x,&y);
  	e[x].push_back(y);
  	e[y].push_back(x);
  }
  dfs1(1,0);
  dfs2(1,0);
  for(int i=2;i<=n;i++) printf("%d ",ans[i]);
  printf("\n");
  return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值