- 给你一棵以 为根的含有 个点的树和一个序列 。
- 要求对于每个 ,求出有多少对 满足 和 的最近公共祖先(即常说的 )。
- 答案对 取模。。
记 表示以 为根的子树中有多少个节点,一遍 dfs
,即可求出所有的 。该步时间复杂度为 。
考虑 的性质:如果两个节点的 为点 ,那么要么其中一个点是点 ,另一个点为 的后代,要么这两个点分属 的两棵子树。
配上图应该好理解一点:
这是我们的原树,然后我们钦定一个点为 :
我们发现,如果两个点中有任意一个点不是 或者 的后代,那么它们的 都不可能是 。
当一个点是 ,另一个点为 的后代时,大概是这样:
红色框线代表我们所选的两点,显然它们的 就是 。大家可以举另外的例子。
当两个点都不是 时,它们必须分属 的两棵子树,其 才是 。
如图,当两个点在 的同一颗子树时,显然它们的 不是 。
只有当两个点分属 的任意两棵子树时,它们的 才是 。
考虑每种情况对答案的贡献:
- 情况一:其中第一个点是 ,另一个点是 的后代,所以总的可能情况为 种。注意!由于本题允许有 的情况,所以一共有 种方案。注意这里不包括第二个点为 的情况。
- 情况二:我们选定一个点在 的一棵子树 中,有 ,另外一个点即有 种情况,注意另外一个点为 的情况上面为考虑,所以这里不需要减一。总方案数为 。枚举 并统计即可。
好,既然如此,我们再来一遍 dfs
,分这两种情况讨论,即可求出答案。当然这两遍 dfs
其实是可以合并为一遍的。可以证明这一步也是 的,所以总的时间复杂度为 ,可以通过更大的数据(比如 )。
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;
}