洛谷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(1≤i≤m),求出有多少对 ( 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 1≤n≤1×104,1≤m≤5×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(1≤i≤n)。该步时间复杂度为
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×(su−1) 种。注意!由于本题允许有 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 su−sy 种情况,注意另外一个点为 u u u 的情况上面为考虑,所以这里不需要减一。总方案数为 s y × ( s u − s y ) s_y \times (s_u-s_y) sy×(su−sy)。枚举 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;
}