[CF1527D] MEX Tree (lca)

31 篇文章 1 订阅
4 篇文章 0 订阅

题面

给你一棵 n n n 个结点的树,对于所有的 k ∈ [ 0 , n ] k\in[0,n] k[0,n] ,求出 M E X = k {\rm MEX}=k MEX=k 的路径数量。

一条路径的 M E X \rm MEX MEX 定义为该路径上没出现的最小结点编号(编号从 0 0 0 n − 1 n-1 n1)。

1 ≤ n ≤ 2 e 5 1\leq n\leq 2e5 1n2e5,多组数据。

题解

一条路径的 M E X \rm MEX MEX 恰好等于 k k k 的充要条件是

  • 结点 0 0 0 k − 1 k-1 k1 都出现在路径上。
  • 结点 k k k 没有出现在路径上。

我们先想想怎么求 0 0 0 i i i 都出现在路径上的路径个数,令其为 f ( i ) f(i) f(i)

如果这 i + 1 i+1 i+1 个点在树上没办法由一条路径经过(形成的虚树不是一条链)那么答案一定为 0,并且 f ( i + 1 ) f(i+1) f(i+1) 直到 f ( n ) f(n) f(n) 的值也都是 0 。在这之前,如果包含它们的最短路径两端是 A A A B B B(不妨令它们没有祖先关系),那么 f ( i ) f(i) f(i) 就为 A A A 端子树大小 × B B B 端子树大小。

M E X \rm MEX MEX 等于 k k k 的答案不难发现可以用一个小容斥算出: f ( k − 1 ) − f ( k ) f(k-1)-f(k) f(k1)f(k)

然后,难点就剩计算 f ( i ) f(i) f(i) 了。首先不妨令 f ( − 1 ) = n ∗ ( n − 1 ) 2 f(-1)=\cfrac{n*(n-1)}{2} f(1)=2n(n1)

接着我们发现,如果把 0 当作根的话,一切都变得明朗。从 0 0 0 i i i 形成的虚树一定都经过根,那么我们用 A A A B B B 两个变量存路径的两端就足够了(初始为 0)。从小到大依次加入点,如果出现加入的点 x x x 分别与 A A A B B B 的最近公共祖先不是 1 , A , B , x 1,A,B,x 1,A,B,x 中任何一个的话,那当前的 f ( i ) f(i) f(i) 以及后面的都是 0 了,就不用继续算了。否则的话,就讨论讨论,看怎么更新 A A A B B B

计算 f ( i ) f(i) f(i) 的时候,如果 A A A B B B 都是 0,那么就是经过 0 的路径数,提前处理一下即可;如果 A A A B B B 其中一个是 0(假设是 A A A),那么先求出 B B B 往上走刚好走到 0 的儿子的那个点 B ′ B' B,答案就是 ( n − s i z [ B ′ ] ) × s i z [ B ] (n-siz[B'])\times siz[B] (nsiz[B])×siz[B];如果 A A A B B B 都大于 0 的话,答案自然是 s i z [ A ] × s i z [ B ] siz[A]\times siz[B] siz[A]×siz[B]

具体实现有点细节多。复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

CODE

#include<cstdio>
#include<vector>
#include<cmath>
#include<map>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 300005
#define DB double
#define LL long long
#define ENDL putchar('\n')
#define lowbit(x) ((-x) & (x))
LL read() {
	LL f=1,x=0;char s = getchar();
	while(s < '0' || s > '9') {if(s=='-')f = -f;s = getchar();}
	while(s >= '0' && s <= '9') {x=x*10+(s-'0');s = getchar();}
	return f * x;
}
int n,m,i,j,s,o,k;
LL as[MAXN];
vector<int> g[MAXN];
int d[MAXN],dfn[MAXN],rr[MAXN],tim,siz[MAXN];
LL dp[MAXN];
int A,B,FLAG,f[MAXN][21];//18
void dfs(int x,int ff) {
	d[x] = d[f[x][0] = ff] + 1;dp[x] = 0;siz[x] = 1;
	dfn[x] = ++ tim;
	for(int i = 1;i <= 18;i ++) f[x][i] = f[f[x][i-1]][i-1];
	for(int i = 0;i < (int)g[x].size();i ++) {
		int y = g[x][i];
		if(y != ff) {
			dfs(y,x);
			dp[x] += siz[x] *1ll* siz[y];
			siz[x] += siz[y];
		}
	}rr[x] = tim;
	return ;
}
int lca(int a,int b) {
	if(d[a] < d[b]) swap(a,b);
	if(d[a] > d[b]) {
		for(int i = 18;i >= 0;i --) {
			if(d[f[a][i]] >= d[b]) a = f[a][i];
		}
	}if(a == b) return a;
	for(int i = 18;i >= 0;i --) {
		if(f[a][i] != f[b][i]) {
			a = f[a][i]; b = f[b][i];
		}
	}return f[a][0];
}
LL cal() {
	if(!FLAG) return 0;
	if(A > B) swap(A,B);
	if(A == 1 && B == 1) return dp[1];
	if(A == 1) {
		int ft = B;
		for(int i = 18;i >= 0;i --) {
			if(d[f[ft][i]] > d[A]) ft = f[ft][i];
		}
		int nm1 = n - (rr[ft]-dfn[ft]+1);
		return (rr[B]-dfn[B]+1) *1ll* nm1;
	}
	return (rr[B]-dfn[B]+1) *1ll* (rr[A]-dfn[A]+1);
}
void addp(int x) {
	if(!FLAG) return ;
	if(A > B) swap(A,B);
	if(A == 1 && B == 1) B = x;
	else if(A == 1) {
		int lc = lca(x,B);
		if(lc == 1) A = x;
		else if(lc == B) B = x;
		else if(lc != x) FLAG = 0;
	}
	else {
		int lc1 = lca(A,x),lc2 = lca(B,x);
		if(lc1 == A) A = x;
		else if(lc2 == B) B = x;
		else if((lc1 == x) || (lc2 == x)) return ;
		else FLAG = 0;
	}return ;
}
int main() {
	int T = read();
	while(T --) {
		n = read();
		tim = 0;A = B = FLAG = 1;
		for(int i = 1;i <= n+1;i ++) {
			g[i].clear(); as[i] = 0;
			memset(f[i],0,sizeof(f[i]));
		}
		for(int i = 1;i < n;i ++) {
			s = read()+1;o = read()+1;
			g[s].push_back(o);
			g[o].push_back(s);
		}
		dfs(1,0);
		as[1] = n*1ll*(n-1)/2ll;
		for(int i = 2;i <= n;i ++) {
			LL ass = cal();
			as[i-1] -= ass;
			as[i] = ass;
			addp(i);
		}
		LL ass = cal();
		as[n] -= ass; as[n+1] = ass;
		for(int i = 1;i <= n+1;i ++) {
			printf("%lld ",as[i]);
		}ENDL;
	}
	return 0;
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值