2020.05.31日常总结

洛谷P5002     专心OI   -   找祖先 \color{green}{\texttt{洛谷P5002\ \ \ 专心OI - 找祖先}} 洛谷P5002   专心OI - 找祖先

[Problem] \color{blue}{\texttt{[Problem]}} [Problem]

  • 给你一棵以 r r r 为根的含有 n n n 个点的树和一个序列 P 1.. m P_{1..m} P1..m
  • 要求对于每个 P i ( 1 ≤ i ≤ m ) P_i(1 \leq i \leq m) Pi(1im),求出有多少对 ( u j , v j ) (u_j,v_j) (uj,vj) 满足 u j u_j uj v j v_j vj 的最近公共祖先(即常说的 LCA \texttt{LCA} LCA)。
  • 答案对 1 × 1 0 9 + 7 1 \times 10^9+7 1×109+7 取模。 1 ≤ n ≤ 1 × 1 0 4 , 1 ≤ m ≤ 5 × 1 0 4 1 \leq n \leq 1 \times 10^4,1 \leq m \leq 5 \times 10^4 1n1×104,1m5×104

[Solution] \color{blue}{\texttt{[Solution]}} [Solution]

s u s_u su 表示以 u u u 为根的子树中有多少个节点,一遍 dfs,即可求出所有的 s i ( 1 ≤ i ≤ n ) s_i(1\leq i \leq n) si(1in)。该步时间复杂度为 O ( n ) O(n) O(n)

考虑 LCA \texttt{LCA} LCA 的性质:如果两个节点的 LCA \texttt{LCA} LCA 为点 u u u,那么要么其中一个点是点 u u u,另一个点为 u u u 的后代,要么这两个点分属 u u u 的两棵子树。

配上图应该好理解一点:

在这里插入图片描述

这是我们的原树,然后我们钦定一个点为 u u u

在这里插入图片描述

我们发现,如果两个点中有任意一个点不是 u u u 或者 u u u 的后代,那么它们的 LCA \texttt{LCA} LCA 都不可能是 u u u

当一个点是 u u u,另一个点为 u u u 的后代时,大概是这样:

在这里插入图片描述

红色框线代表我们所选的两点,显然它们的 LCA \texttt{LCA} LCA 就是 u u u。大家可以举另外的例子。

当两个点都不是 u u u 时,它们必须分属 u u u 的两棵子树,其 LCA \texttt{LCA} LCA 才是 u u u

在这里插入图片描述

如图,当两个点在 u u u 的同一颗子树时,显然它们的 LCA \texttt{LCA} LCA 不是 u u u

在这里插入图片描述

只有当两个点分属 u u u 的任意两棵子树时,它们的 LCA \texttt{LCA} LCA 才是 u u u

考虑每种情况对答案的贡献:

  • 情况一:其中第一个点是 u u u,另一个点是 u u u 的后代,所以总的可能情况为 1 × ( s u − 1 ) 1 \times (s_u-1) 1×(su1) 种。注意!由于本题允许有 u j = v j u_j=v_j uj=vj 的情况,所以一共有 s u s_u su 种方案。注意这里不包括第二个点为 u u u 的情况。
  • 情况二:我们选定一个点在 u u u 的一棵子树 y y y 中,有 s y s_y sy,另外一个点即有 s u − s y s_u-s_y susy 种情况,注意另外一个点为 u u u 的情况上面为考虑,所以这里不需要减一。总方案数为 s y × ( s u − s y ) s_y \times (s_u-s_y) sy×(susy)。枚举 y y y 并统计即可。

好,既然如此,我们再来一遍 dfs,分这两种情况讨论,即可求出答案。当然这两遍 dfs 其实是可以合并为一遍的。可以证明这一步也是 O ( n ) O(n) O(n) 的,所以总的时间复杂度为 O ( n ) O(n) O(n),可以通过更大的数据(比如 1 × 1 0 6 1 \times 10^6 1×106)。

[code] \color{blue}{\texttt{[code]}} [code]

const int N=1e4+100;
int ans[N],size[N],n,m;
struct edge{//链式前向星 
	int next,to;//两量存图 
}e[N<<1];int h[N],tot,root;
inline void add(int a,int b){
	e[++tot]=(edge){h[a],b};h[a]=tot;
	e[++tot]=(edge){h[b],a};h[b]=tot;
}
const int mod=1e9+7;//记得对1e9+7取模 
inline int f(int a,int b){//方便使用 
	return (size[a]-size[b])*size[b]%mod;
}//情况二的方案数计算(b为指定儿子) 
void dfs_init_and_calc(int u,int fa){
	size[u]=1;//注意初始!u也是有大小的! 
	for(int i=h[u];i;i=e[i].next){//遍历 
		register int to=e[i].to;//to:儿子 
		if (to==fa) continue;//可行性判断 
		dfs_init_and_calc(to,u);//递归计算 
		size[u]+=size[to];//累加子树的大小 
	}//前半段:求以u为根的子树的大小size[u] 
	for(int i=h[u];i;i=e[i].next){//遍历 
		register int to=e[i].to;//to:儿子 
		if (to==fa) continue;//可行性判断 
		ans[u]=(ans[u]+f(u,to))%mod;//更新 
	}
	ans[u]=(ans[u]+size[u])%mod;
//	注意LCA(u,u)也等于u,这也是方案 
}//两遍 dfs 可以合并为一遍
int main(){
//	freopen("t1.in","r",stdin);
	scanf("%d%d%d",&n,&root,&m);
	for(int i=1,u,v;i<n;i++){
		scanf("%d%d",&u,&v);
		add(u,v);//加入边(u,v) 
	}
	dfs_init_and_calc(root,-1);
	for(int i=1,u;i<=m;i++){
		scanf("%d",&u);
		printf("%d\n",ans[u]);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值