P2767 树的数量 DP | 组合数学

题面:

给定(n,m)(n)个节点的(m)叉树的形态有多少种

范围&性质:(1le n,mle 127)

分析:

  • DP做法

(m)等于2时就是卡特兰数,详情见卡特兰数定义和递推式

那我们考虑像Catlan数一样枚举每个儿子的大小然后组合起来,所以设(f[i][j])表示表示根节点有(i)(m)叉子树,总个数为(j)的方案数,(g[i])表示用(i)个点构成一颗(m)叉树的方案数,转移时相当于往根节点上接一颗(m)叉树

[f[i][j]=sum g[k] imes f[i-1][j-k] ]

为了消掉转移对原有状态的影响,还应该更新(g)数组

[g[i]=f[m][i] ]

复杂度(O(n^3))

  • 组合做法

我们将每个节点有几个儿子记录下来,按照编号会组成一个序列,序列里的每一项值的大小不会超过(m),序列一共有(n)项,每一个合法的序列一定满足元素之和等于(n-1),也就是说它的组合意义就是从(n*m)个点中选出(n-1)个的合法方案数,我们发现按照(dfs)序选儿子,每次能选的点会变少(因为已经被选过的点是不能选的)

所以对于所有情况合法的方案占总方案数的(prod_{i=1}^{n-1} frac{i}{i+1}=frac{1}{n})

最后总的答案就是(ans=frac{C(n*m,n-1)}{n})

代码:

#include<bits/stdc++.h>

using namespace std;

namespace zzc
{
	const int mod = 10007;
	long long n,m;
	long long fac[mod+5],inv[mod+5];
	
	void init()
	{
		fac[0]=fac[1]=1;
		inv[0]=inv[1]=1;
		for(int i=2;i<=mod;i++)
		{
			fac[i]=fac[i-1]*i%mod;
			inv[i]=inv[mod%i]*(mod-mod/i)%mod;
		}
		for(int i=2;i<=mod;i++) inv[i]=inv[i]*inv[i-1]%mod;
	}
	
	long long qpow(long long x,long long y)
	{
		long long res=1;
		while(y)
		{
			if(y&1) res=res*x%mod;
			x=x*x%mod;
			y>>=1;
		}
		return res;
	}
	
	long long C(long long n,long long m)
	{
		if(m>n) return 0;
		return fac[n]*inv[n-m]%mod*inv[m]%mod;
	}
	
	long long lucas(long long n,long long m)
	{
		if(!m) return 1;
		return lucas(n/mod,m/mod)*C(n%mod,m%mod)%mod;
	}
	
	void work()
	{
		init();
		scanf("%lld%lld",&n,&m);
		printf("%lld
",lucas(n*m,n-1)*qpow(n,mod-2)%mod);
	}

}

int main()
{
	zzc::work();
	return 0;
}
原文地址:https://www.cnblogs.com/youth518/p/13823539.html