2020.7.17 T1亲戚(jz暑假训练day3)

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]);
}

猜你喜欢

转载自blog.csdn.net/jay_zai/article/details/107415794