《训练指南》——6.10
Uva11174:
村民排队:村子里现在有n(1≤n≤40000)个人,有多少种方式可以把它们排成一列,使得没有人站在他父亲的前面(有些人的父亲可能不在村子里)?输入n和每个人的父亲编号,输出方案总数除以1000000007的余数。
分析:首先我们应该能够看到的是,这种有关家庭关系的图,需要借助基本的一个数据结构——树,但是这里由于各个祖先不同,会形成森林,但是为了更好处理,我们将森林中的根节点的上面构造一个虚拟根,记作v0,这样就形成了一棵树。
我们设f[i]是以节点vi为根,得到的排列情况书,即f[0]就是这道题目的答案。
那么我们现在来考察f[0]究竟怎么计算,这里用到基本的计数原理。我们假设虚拟根v0下有k个儿子v1、v2、v3…vk,相当于有k个家族,则我们可以通过如下的步骤得到f[0]:
(i) 我们将每个家族的人视为一样的元素.
(ii) 得到排列数之后再乘上这个家族符合要求的排列数.
这里记s[i]是第i个家族的人数,也就是说以vi为根的所有节点的和(包括vi自己),那么完成第一个步骤,我们得到的方法数是
而完成第二个步骤,结合分步乘法原理,应该是如下的式子:
这样我们便得到了一个关于f[n]的递推式,如下:
其中k是v0的儿子个数,如果我们设置一个数组son[i]记录vi的儿子儿子数,ci表示节点的i个儿子在树结构中的序号,因此我们会得到更加具有普遍性的递推式子:
那么现在将②迭代到①当中,会发现一直递推下去,再考虑对于叶节点vj,有f[j] = 1,因此我们能够得到如下的线性表达式:
那么基于这个线性表达式,接下来我们需要考虑的事情就很明了了:
问题一:如何基于树结构找到s[i]呢,这是最基本的数据结构的问题。
问题二:题目要求输出f[0] % mod,而根据数论当中同余运算的性质,涉及出发应该借助逆元,而结合mod的大小和s[i]的取值,有gcd(mod , s[i]),我们也是能够得到s[i]在关于mod下一定存在逆元的。
总结一下,这道题目还是一道非常好的题目的,它涉及了组合计数的基本原理、递推、整理数学公式、基本树形数据结构和数论当中的逆元,是一道非常综合的题目。这里笔者由于临近考试周时间紧张的原因,在如何编码实现计算s[i]和求解逆元上先不做讨论,以后有时间了再补充。