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