Description
Input
Output
Sample Input
4
0 1 1 0
Sample Output
8
Data Constraint
赛时
25分暴力+5分全排列
正解
树形dp
方程是:
f[x]=((f[x] * f[v])%mod*c(min(size[x]-1,size[v]),size[x]-1+size[v]))%mod;
x为当前遍历的点,v为儿子,此时的size[x]并没有加上v的节点数。那么这个方程的意思也就是v的子树与前面x遍历过的子树求方案,f[x]*f[v]先是匹配,之后的c里面的东西,就是指在前面子树和v的子树的节点数之和中(就是前面的size[x]-1+size[v],这里-1是因为x必须在最前面,所以x的位置不能放),放v子树节点个数的方案数(min(size[x]-1,size[v]),这里用min是因为如果前面子树节点数之和比v的节点数多,那么我们就求放前面子树节点数之和的方案数,其实是一样的)
那么dp部分就是这样,现在考虑c的求法。
c(n,m)指在m个位置中放n个数的方案,它的答案就是m!/n!(n-m)!,所以一开始先把阶乘预处理好,之后由于要mod10^9+7(质数),而且我们知道m%p / n%p!=m/n%p,那么这个时候就需要用乘法逆元了,乘法逆元是指mod p的意义下ax同余1时,x就是a的逆元。
然后逆元可以用费马小定理或者扩展欧几里得,欧拉来做,这里选择费马。
那么上面式子就是费马小定理了,这个时候变一下就是a * a^(p-2)同余1(mod p),我们知道10^9+7是质数,那么a mod 10^9+7的情况下,逆元就是a ^p-2,这里再用快速幂加速就行了。
这里还有一种求法,答案其实就是n!/size[1]/size[2]/size[3]/…/size[n],也就是n的阶乘除以每一个子树的大小,至于证明就不清楚了,是从他人博客找到的,有大佬也证明了,但是个人感觉不太严谨就不展开讲了。这个求法本人没打,但是绝对是正确的,而且也比dp更简单
下面给上第一种方法的代码:
#include<cstdio>
#include<iostream>
#include<cstring>
#define N 200006
#define mod 1000000007
#define ll long long
using namespace std;
struct node{
int to,nxt;
}e[2*N];
int head[4*N],cnt;
int size[N],n,x;
ll f[N],jc[N];
void add(int u,int v){
e[++cnt].to=v;
e[cnt].nxt=head[u];
head[u]=cnt;
}
ll fast(ll x,ll y){//快速幂
ll ans=x,res=1;
while(y>1){
if(y%2==1)
res=(res*ans)%mod;
ans=(ans*ans)%mod;
y/=2;
}
return (res*ans)%mod;
}
ll c(ll n,ll m){//c公式
return jc[m]*fast(jc[m-n]*jc[n]%mod,mod-2)%mod;
}
void dfs(int fa,int x){
size[x]=1;
f[x]=1;
for(int i=head[x];i;i=e[i].nxt){
int v=e[i].to;
if(v==fa) continue;
dfs(x,v);
f[x]=((f[x]*f[v])%mod*c(min(size[x]-1,size[v]),size[x]-1+size[v]))%mod;
size[x]+=size[v];
}
}
int main(){
jc[0]=1;
for(int i=1;i<N;i++)//预处理阶乘
jc[i]=(jc[i-1]*i)%mod;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&x),add(x,i),add(i,x);//链式前向星
dfs(0,0);
printf("%lld",f[0]);
}