2020.05.31日常总结

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

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

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

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

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

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

配上图应该好理解一点:

在这里插入图片描述

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

在这里插入图片描述

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

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

在这里插入图片描述

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

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

在这里插入图片描述

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

在这里插入图片描述

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

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

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

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

[code]\color{blue}{\texttt{[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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章