ARC106F Figures / A.圣诞树 题解(Prufer 序列的应用)
有 \(n\) 个点,第 \(i\) 个点上有 \(a_i\) 个可区分的洞,问在这些洞之间连 \(n-1\) 条线使得形成一棵树的方案数。对给定的模数 \(m\) 取模。
\(n\le 10^6,a_i\le m\)。
这个题有点厉害。
我这个解法需要用到 prufer 序列的一个结论。首先 prufer 序列是一个将 \(n\) 个点的带标号无根树与长度为 \(n-2\),值域为 \(1\sim n\) 的整数序列一一对应的一种构造方案。具体构造方案如下:
进行以下操作直到只剩下两个点:
- 拿出所有叶子中编号最小的点 \(x\)。
- 与 \(x\) 相邻的唯一一个点加入 prufer 序列。
- 将 \(x\) 从这棵树里删除。
因为最后只剩下两个点,所以这个序列长度为 \(n-2\)。
这个序列就被我们称为 prufer 序列。prufer 序列有非常多良好的性质。
首先,根据上述构造过程,我们可以知道一个度数为 \(d_x\) 的点 \(x\) 在 prufer 序列中恰好出现了 \(x-1\) 次,因为只有恰好一次删除与 \(x\) 相邻的边时,没有在 prufer 序列里记录 \(x\)。因此所有没出现过的点就是叶子节点。
其次,prufer 序列和有标号无根树是一一对应的关系,因为你可以这么根据 prufer 序列还原一棵树:
拿出所有没有在 prufer 序列中出现的点,这个集合记为 \(S\)。
重复以下操作直到 prufer 序列为空:
- 令当前 \(S\) 集合中编号最小的点为 \(x\),prufer 序列的第一个元素为 \(y\)。
- 在树中添加一条边 \((x,y)\)。从 \(S\) 中删去 \(x\),删除 prufer 序列的第一个元素。
- 如果 \(y\) 在 prufer 序列中不再出现(即现在 \(y\) 也是一个叶子节点了),则将 \(y\) 加入到集合 \(S\)。
在进行了 \(n-2\) 次上述操作后,集合 \(S\) 中应该恰有两个点,再在这两个点之间连边即可。
根据以上构造过程,我们就证明了一个 prufer 序列也只能恰好被还原为一个带标号无根树。因此 prufer 序列和无根树是双射关系。
那么接下来,如果给定了一个度数序列 \(d_1,d_2,\cdots d_n\),即代表 \(i\) 的度数是 \(d_i\),如何求有多少个无根树满足这个度数序列呢?这个时候就要用到 prufer 序列的性质,即度数为 \(i\) 的点出现了 \(d_i-1\) 次,那么也就是,任意一个满足 \(i\) 出现了 \(d_i-1\) 次的 prufer 序列就对应了一个满足条件的无根树。这个无根树一共有多少个呢?显然是 \(\dfrac{(n-2)!}{\prod_{i=1}^n(d_i-1)!}\) 个。
回到我们的问题来。我们可以先枚举一个度数序列 \(d_i\),答案即为
其中 \(a_i^{\underline{d_i}}\) 代表 \(a_i\) 的 \(d_i\) 次下降幂,也就是 \(a_i\times (a_i-1)\times\cdots\times(a_i-d_i+1)\)。
不妨令新的 \(d_i\) 等于原来的 \(d_i-1\),这样看着好看一点。式子转为:
由于两个 \(\prod\) 的 \(i\) 上下界都一样,我们不妨直接把 \(d_i!\) 放进后面的 \(\prod\) 里,这一步是为了把 \((n-2)!\) 提到求和式外边,方便以后化简:
后面这个 \(\dfrac{a_i^{\underline{d_i+1}}}{d_i!}\) 我们想把它转换成一个组合数的形式,因为上面是一段下降幂连乘,下面是一个除以一个阶乘。发现问题在于上方分子多了一个 \(a_i\),我们直接把这个 \(a_i\) 也提到外边:
然后回忆一下范德蒙德卷积,范德蒙德卷积是在说这么一个事:如果你要从 \(n\) 个男生 \(m\) 个女生中选出 \(k\) 个人,方案数是 \(\binom{n+m}{k}\)。但是你也可以这么想,你枚举你选了多少个男生,设为 \(i\),那也就是你要选 \(k-i\) 个女生,也就是 \(\sum_{i=1}^k\binom{n}{i}\binom{m}{k-i}\)。这两个式子的组合意义是相同的,也就是有恒等式:
同理,我们不妨扩展一下这个式子,有 \(p\) 元形式的恒等式:
重新观察我们上面这个答案式子,发现后面这一项其实就是要你在 \(\sum (a_i-1)=\sum a_i-n\) 项里面选 \(n-2\) 项,因此化简为:
把 \((n-2)!\) 乘进这个组合数里面(避免算逆元,因为 \(m\) 不一定是质数),得到:
直接算即可。
void work() {
int n, mod; cin >> n >> mod;
vector<int> a(n + 1);
for (int i = 1; i <= n; ++i)
cin >> a[i];
int prod = 1, sum = 0;
for (int i = 1; i <= n; ++i) {
prod = prod * a[i] % mod;
sum = (sum + a[i]) % mod;
}
sum = ((sum - n) % mod + mod) % mod;
int ans = prod;
for (int i = 0; i < n - 2; ++i) {
ans = (ans * (sum - i + mod) % mod) % mod;
}
cout << ans << endl;
}

浙公网安备 33010602011771号