思路比较简单但是很经典的一题,最近一直在补数学。。
用dp(u)表示以节点u为根的种类数,则由基本的组合数学可以得出\[dp(u)=(\frac{(n-1)!}{\prod size(v)})*\prod dp(v),\ u\ is\ v’s\ parent\] 其中\[size(v)\]是以v为根的子树的大小。然后由于这里模值是任取的,所以不能用求逆元的方式来做,否则的话也太简单了。
考虑进一步化简这个式子,把\[dp(v)\] 们展开(脑海中展开)可以发现对于每个节点来说,他以 \[(size(v)-1)!\]的形式出现在分子上一次,以\[size(v)!\]的形式出现在分母上一次,所以最终把所有节点(不仅仅是root的直接儿子)全部代进去就可以得到\[dp(root)=(\frac{(n-1)!}{\prod_{v=2}^{v=n} size(v)})\]
题目有个坑点的话是dfs会爆栈然后返回wa,改成bfs就过了。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int mod=998244353;
const int maxn=5e5+10;
LL p[maxn],tot;
bool isp[maxn];
int n,m;
void eular(){
LL tmp;
for(int i=2;i<maxn;i++){
if(isp[i]==0) p[++tot]=i;
for(int j=1;j<=tot;j++){
tmp=p[j]*i;
if(tmp>=maxn) break;
isp[tmp]=1;
if(i%p[j]==0) break;
}
}
}
bool vis[maxn];
vector<int> G[maxn];
int gl[maxn],chs[maxn],fa[maxn],que[maxn],head,tail;
// int dfs1(int x){
// chs[x]=1;
// for(int i=0;i<gl[x];i++){
// chs[x]+=dfs1(G[x][i]);
// }
// return chs[x];
// }
void bfs(){
head=tail=0;
que[tail++]=1;
while(head!=tail){
int cur=que[head++];
chs[cur]=1;
for(int i=0;i<gl[cur];i++){
que[tail++]=G[cur][i];
}
}
for(int i=tail-1;i;i--){
chs[fa[que[i]]]+=chs[que[i]];
}
}
int cnt[maxn];
int tt,op,mt;
void Div(LL A,int f){
tt=0;
for(LL i=1;p[i]*p[i]<=A&&(isp[A]);i++){
if(A%p[i]==0){
tt=p[i];
while(A%p[i]==0){
A/=p[i];
cnt[tt]+=f;
}
}
}
if(A!=1){
tt=A;
cnt[A]+=f;
}
mt=max(mt,tt);
}
LL C(){
for(int i=1;i<=n-1;i++)
Div(i,1);
for(int i=2;i<=n;i++)
Div(chs[i],-1);
LL res=1;
for(int i=1;p[i]<=mt;i++){
while(cnt[p[i]]){
res*=p[i];
if(res>=mod) res%=mod;
cnt[p[i]]--;
}
}
return res;
}
void init(){
mt=tt=0;
memset(cnt,0,sizeof(cnt));
}
int main(){
eular();
int t,v;
scanf("%d",&t);
while(t--){
init();
scanf("%d%d",&n,&m);
mod=m;
for(int i=1;i<=n;i++) G[i].clear(),gl[i]=0;
for(int i=2;i<=n;i++){
scanf("%d",&v);
G[v].push_back(i);
fa[i]=v;
}
for(int i=1;i<=n;i++) gl[i]=G[i].size();
bfs();
LL ans=C();
printf("%lld\n",ans);
}
return 0;
}
