- 給你一棵以 爲根的含有 個點的樹和一個序列 。
- 要求對於每個 ,求出有多少對 滿足 和 的最近公共祖先(即常說的 )。
- 答案對 取模。。
記 表示以 爲根的子樹中有多少個節點,一遍 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;
}