Description
今年的NOIP初赛真是简单,小可可不用吹灰之力就考进了复赛,但是复赛可没有那么简单了,小可可想要好好准备复赛,争取复赛拿个省一。今天小可可在复习树和图的最大匹配时就碰到这样的一个难题:n个节点满足以下性质的不同的树有多少种。
1、树是有标号的,每个节点被标上1到n之间的整数;
2、每个节点最多和其他3个节点相连,但是1号节点最多和其他2个节点相连;
3、这棵树的最大匹配(把树看成二分图后的最大匹配)数为k。
两棵树被认为不同当且仅当存在两个点u、v,在一棵树中u、v之间有边,另一棵树中u、v之间没边。
由于答案可能很大,所以小可可让你输出答案模1000000007 (10^9 + 7)。
Data Constraint
对于30%的数据,2≤n≤5;
对于60%的数据,2≤n≤20;
对于100%的数据,2≤n≤50。
Solution
这是一道显然的数学题,而本人最讨厌数学题……
我们设出f[i][j][0..1]表示当前树的大小为i,最大匹配为j的情况。若根节点被选择,则为1,否则为0。那么我们考虑两种情况:
1、当前i仅有一个儿子。 因为要保证最大匹配,所以转移显然有
f[i+1][j][0]+=(i+1)∗f[i][j][1],f[i+1][j+1][1]+=(i+1)∗f[i][j][0]
。
2、当前i有两个儿子,大小分别为x,y,两个儿子的最大匹配分别为a,b。那么转移同样显然。
f[x+y+1][a+b+1][0]+=(x+y+1)∗f[x][a][1]∗f[y][b][1]∗Cxx+y2
。
Cxx+y
是因为存在两个点连边情况不同才被认为不同方案,所以我们要选出大小为x的子树放了哪些点。最后除以2是因为会出现重复的情况,即x,y调转角色。同时还有
f[x+y+1][a+b+1][1]+=(x+y+1)∗f[x][a][0]∗f[y][b][0]+)∗Cxx+y2+(x+y+1)∗f[x][a][0]∗f[y][b][1]∗Cxx+y
。解释同上。
Code
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
const ll maxn=55,mo=1e9+7;
ll f[maxn][maxn][2],c[maxn];
ll n,m,i,t,j,k,l,x,y,ans;
ll mi(ll x,ll y){
if (y==1) return x;
ll t=mi(x,y/2);
if (y%2) return t*t%mo*x%mo;return t*t%mo;
}
ll dg(int x,int y){
return c[x]*mi(c[y]*c[x-y]%mo,mo-2)%mo;
}
int main(){
// freopen("data.in","r",stdin);
scanf("%lld%lld",&n,&m);c[0]=1;
for (i=1;i<=n;i++)
c[i]=c[i-1]*i%mo;
f[1][0][0]=1;
for (i=2;i<=n;i++)
for (j=0;j<=min(i/2,m);j++){
f[i][j][0]=i*f[i-1][j][1]%mo;
if (j) f[i][j][1]=i*f[i-1][j-1][0]%mo;
for (x=1;x<i-1;x++)
for (k=0;k<=min(x/2,j);k++){
y=i-1-x;
l=j-k;
if (y/2>=l) f[i][j][0]=(f[i][j][0]+i*f[x][k][1]%mo*f[y][l][1]%mo*dg(i-1,x)%mo*mi(2,mo-2))%mo;
l--;if (y/2<l || l<0) continue;
f[i][j][1]=(f[i][j][1]+i*f[x][k][1]%mo*f[y][l][0]%mo*dg(i-1,x)%mo)%mo;
f[i][j][1]=(f[i][j][1]+i*f[x][k][0]%mo*f[y][l][0]%mo*dg(i-1,x)%mo*mi(2,mo-2))%mo;
}
}
ans=(f[n][m][0]+f[n][m][1])%mo*mi(n,mo-2)%mo;
printf("%lld\n",ans);
}