Clarke and tree
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)
Total Submission(s): 134 Accepted Submission(s): 62
Problem Description
Clarke is a patient with multiple personality disorder. One day, Clarke turned into a CS, did a research on data structure.
Now Clarke has n nodes, he knows the degree of each node no more than
ai
a
i
. He wants to know the number of ways to choose some nodes to compose to a tree of size
s(1≤s≤n)
s
(
1
≤
s
≤
n
)
.
Input
The first line contains one integer
T(1≤T≤10)
T
(
1
≤
T
≤
10
)
, the number of test cases.
For each test case:
The first line contains an integer
n(2≤n≤50)
n
(
2
≤
n
≤
50
)
.
Then a new line follow with n numbers. The ith number
ai(1≤ai<n)
a
i
(
1
≤
a
i
<
n
)
denotes the number that the degree of the
ith
i
t
h
node must no more than
ai
a
i
.
Output
For each test case, print a line with n n integers. The number denotes the number of trees of size i i modulo .
Sample Input
1
3
2 2 1
Sample Output
3 3 2
Hint:
At first we know the degree of node 1 can not more than 2, node 2 can not more than 2, node 3 can not more than 1. So
For the trees of size 1, we have tree ways to compose, are 1, 2 and 3. i.e. a tree with one node.
For the trees of size 2, we have tree ways to compose, are 1-2, 1-3, 2-3.
For the trees of size 3, we have two ways to compose, are 1-2-3, 2-1-3.
传送门!!
有
n
n
个点,第个点的限制为度数不能超过
ai
a
i
。
现在对于每一个
s(1≤s≤n)
s
(
1
≤
s
≤
n
)
,问从这
n
n
个点中选出个点组成有标号无根树的方案数。
解:
我们首先知道prufer序列。在所选节点定下来之后,每一个长度为n的prufer序列对应且唯一对应一棵节点为n+2的树,而且有一个很好的性质,就是说所有的prufer序列和所有的树是一一对应的。也就是说,求树的计数和prufer序列计数是等价的。
那么这道题我们就有一个很好的想法:
fi,j
f
i
,
j
表示选到第
i
i
个点,prufer序列长度为的方案数有多少。转移是
fi+1,j+k+=fi,j∗Cjj+k
f
i
+
1
,
j
+
k
+
=
f
i
,
j
∗
C
j
+
k
j
。
为什么呢?我们现在有一个长度为
x
x
的prufer序列,我们向里面加入个点的方案有多少种?由于这
k
k
个点是相同的,所以我们可以换一种思路:现在已经有个点了,我们在中间选
x
x
个位置,把原来的序列排进去。方案数是等价的。
但是这样写写发现是wa的,只有选出个点组成数的时候是对的。是为什么呢?在看一看上面的描述:在所选节点定下来之后,每一个prufer序列才唯一对应一棵树。什么意思?我们现在有5个点可以选,而prufer序列是1,2。剩下在树里的两个点可能是3,4,5里选两个。由此可见这个prufer序列对应的树实际上有3个。而我们的dp方程只是prufer序列计数,并没有记录树的个数。
我们用
fi,j,k
f
i
,
j
,
k
表示选到前
i
i
个点,选了个点且prufer序列长度为
k
k
的方案数。值得注意的是选了某一个点并不代表它在prufer序列中,因为我们如果选这个点度数为1的话,它并不会出现在prufer序列中。
转移:枚举下一个点的度数
如果
d=0
d
=
0
,表示不把这个点放进prufer序列中,
fi+1,j,k+=fi,j,k
f
i
+
1
,
j
,
k
+
=
f
i
,
j
,
k
如果
d≠0
d
≠
0
,表示把这个点选入prufer序列中,
fi+1,j+1,k+d−1+=fi,j,k∗Ckk+d−1
f
i
+
1
,
j
+
1
,
k
+
d
−
1
+
=
f
i
,
j
,
k
∗
C
k
+
d
−
1
k
。
d=1
d
=
1
时相当于放0个点到prufer序列中。
这样我们dp状态就记录了这个状态下的树的个数。因为dp中计算了不在prufer序列中的点,所以没问题了。
下面贴代码,有问题可以看看转移方程:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int n,T;
long long C[105][105];
long long const mod=1000000007;
long long f[105][105][105];
long long a[105];
int main()
{
scanf("%d",&T);
while(T--)
{
memset(f,0,sizeof(f));
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]);
for(int i=0;i<=n;i++)
C[i][0]=1,C[i][i]=1;
for(int i=2;i<=n;i++)
for(int j=1;j<i;j++)
C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
cout<<C[5][3]<<endl;
f[0][0][0]=1;
for(int i=0;i<n;i++)
for(int j=0;j<=i;j++)
for(int k=0;k<=n-2;k++)
for(int d=0;d<=a[i+1];d++)
if(k+d<=n)
{
if(d==0) f[i+1][j][k]=(f[i+1][j][k]+f[i][j][k])%mod;
else f[i+1][j+1][k+d-1]=(f[i+1][j+1][k+d-1]+f[i][j][k]*C[k+d-1][k])%mod;
}
printf("%d",n);
for(int i=2;i<=n;i++)
printf(" %lld",f[n][i][i-2]);
printf("\n");
}
}
丧病oj输出居然不去除行末空格。居然以前交的时候没发现。