题目大概是说给一棵树的n个结点从1到n编号,要求每个结点的编号大于其父结点,问有多少种编号方式。
想了挺久的,感觉有点眉目,最后画了下样例YY出解法:
- 首先求出以每个结点为根的子树大小,记为size[u],这个DFS一遍就可以求出来;
- 接下来,dp[u]表示给以u为根的子树size[u]个编号有几种编号方案 ;
- 然后考虑转移方程:
- 比如一个结点u有3个儿子v1,v2,v3,那么u子树有size[u]个编号,编号最小的就属于u,剩下size[u]-1分配给u的三个子树,分配方式就有:
C(size[u]-1,size[v1])*C(size[u]-1-size[v1],size[v2])*C(size[u]-1-size[v1]-size[v2],size[v3]) 种
-
- 而v1、v2和v3子树对它们被分配的编号又分别有dp[v1]、dp[v2]和dp[v3]种编号方案,因此u子树的size[u]个编号总共的编号方式即dp[u]是:
dp[u] = dp[v1]*dp[v2]*dp[v3]*C(size[u]-1,size[v1])*C(size[u]-1-size[v1],size[v2])*C(size[u]-1-size[v1]-size[v2],size[v3])
于是就是这样用DP求解的。另外C(n,m)可以利用组合数的递推式C(n,m)=C(n-1,m)+C(n-1,m-1)预处理出来。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 struct Edge{ 6 int v,next; 7 }edge[1111]; 8 int NE,head[1111]; 9 void addEdge(int u,int v){ 10 edge[NE].v=v; edge[NE].next=head[u]; head[u]=NE++; 11 } 12 int n,size[1111]; 13 long long C[1111][1111],d[1111]; 14 int getSize(int u){ 15 if(size[u]) return size[u]; 16 int res=1; 17 for(int i=head[u]; i!=-1; i=edge[i].next){ 18 int v=edge[i].v; 19 res+=getSize(v); 20 } 21 return size[u]=res; 22 } 23 long long dfs(int u){ 24 if(d[u]) return d[u]; 25 long long res=1; 26 int cnt=size[u]-1; 27 for(int i=head[u]; i!=-1; i=edge[i].next){ 28 int v=edge[i].v; 29 res*=(dfs(v)*C[cnt][size[v]])%1000000007; 30 res%=1000000007; 31 cnt-=size[v]; 32 } 33 return res; 34 } 35 int main(){ 36 for(int i=0; i<1111; ++i) C[i][0]=1; 37 for(int i=1; i<1111; ++i){ 38 for(int j=1; j<=i; ++j) C[i][j]=(C[i-1][j]+C[i-1][j-1])%1000000007; 39 } 40 int t,a,b; 41 scanf("%d",&t); 42 for(int cse=1; cse<=t; ++cse){ 43 NE=0; 44 memset(head,-1,sizeof(head)); 45 bool uroot[1111]={0}; 46 scanf("%d",&n); 47 for(int i=1; i<n; ++i){ 48 scanf("%d%d",&a,&b); 49 addEdge(a,b); 50 uroot[b]=1; 51 } 52 int root; 53 for(int i=1; i<=n; ++i){ 54 if(!uroot[i]){ 55 root=i; 56 break; 57 } 58 } 59 memset(size,0,sizeof(size)); 60 getSize(root); 61 memset(d,0,sizeof(d)); 62 printf("Case %d: %lld\n",cse,dfs(root)); 63 } 64 return 0; 65 }
时间: 2024-12-25 19:04:27