题意
求:在所有 n n n 个点 m m m 条边的无向简单连通图中,满足把第 i i i 个点删去后图被分为 a i a_i ai 个连通块。
n − 1 ≤ m ≤ n + 1 n-1\le m\le n+1 n−1≤m≤n+1。
思路
将 m = n − 1 , m = n , m = n + 1 m=n-1,m=n,m=n+1 m=n−1,m=n,m=n+1 三种情况进行分类讨论。
对于 m = n − 1 m=n-1 m=n−1,显然是一棵树,每个 a i a_i ai 即为 i i i 的子树数量+父亲。此时需要用到 Prufer 序列(可以看看这篇博客),答案为: n n n 个点的完全生成树中第 i i i 个节点的度数为 a i a_i ai 的方案数,即为:
( n − 2 ) ! ∏ i = 1 n ( a i − 1 ) ! \cfrac{(n-2)!}{\prod^n_{i=1}(a_i-1)!} ∏i=1n(ai−1)!(n−2)!
当 m = n m=n m=n 时,相当于在树上加上一条边形成一个环。我们把环上的边都删除,开一个编号为 n + 1 n+1 n+1 的新点与环上的点连边。这样原图就又转化成了一棵 n + 1 n+1 n+1 个点 n n n 条边的树,而第 n + 1 n+1 n+1 个点的度数为环上点的个数。举个例子:
- 这是一张 6 6 6 个点 6 6 6 条边的图,其中红色数组代表 a i a_i ai 的值。而环的大小为 4 4 4
可以发现:环的大小就是点数乘二再减去度数之和,即
a n + 1 = 2 n − ∑ i = 1 n a i a_{n+1}=2n-\sum^n_{i=1}a_i an+1=2n−i=1∑nai
根据上一种情况,再乘上环上点的排列方案 A a n + 1 a n + 1 2 a n + 1 = ( 2 n − ( ∑ i = 1 n a i ) − 1 ) ! 2 \cfrac{A^{a_{n+1}}_{a_{n+1}}}{2a_{n+1}}=\cfrac{\big(2n-(\sum^n_{i=1}a_i\big)-1)!}{2} 2an+1Aan+1an+1=2(2n−(∑i=1nai)−1)!,答案即为
( n − 1 ) ! ( ∏ i = 1 n ( a i − 1 ) ! ) ( 2 n − ( ∑ i = 1 n a i ) − 1 ) ! × ( 2 n − ( ∑ i = 1 n a i ) − 1 ) ! 2 = ( n − 1 ) ! 2 ∏ i = 1 n ( a i − 1 ) ! \begin{aligned}&~~~~~\cfrac{(n-1)!}{\big(\prod^n_{i=1}(a_i-1)!\big)(2n-\big(\sum^n_{i=1}a_i)-1\big)!}\times\cfrac{(2n-(\sum^n_{i=1}a_i)-1)!}{2}\\&=\cfrac{(n-1)!}{2\prod^n_{i=1}(a_i-1)!}\end{aligned} (∏i=1n(ai−1)!)(2n−(∑i=1nai)−1)!(n−1)!×2(2n−(∑i=1nai)−1)!=2∏i=1n(ai−1)!(n−1)!
当 m = n + 1 m=n+1 m=n+1 时,相当于在上一种情况再多加一个环。进行分类讨论:
- 两个环无公共边
此时我们可以将两个环分别缩成 n + 1 , n + 2 n+1,n+2 n+1,n+2 两个点,仿照 m = n m=n m=n 进行连边,最终会得到 n + 2 n+2 n+2 个点 n + 1 n+1 n+1 条边的树。再举个例子:
- 这是一张 9 9 9 个点 10 10 10 条边的图,左环大小为 5 5 5,右环大小为 4 4 4。
区别在于,此时只能算出两个环的大小之和,答案类似,即
a n + 1 + a n + 2 = 2 n − ∑ i = 1 n a i + 2 a_{n+1}+a_{n+2}=2n-\sum^n_{i=1}a_i+2 an+1+an+2=2n−i=1∑nai+2
我们设 2 n − ∑ i = 1 n a i = s u m 2n-\sum^n_{i=1}a_i=sum 2n−∑i=1nai=sum。两点的度数都至少为 3 3 3(否则不成环),我们枚举 a n + 1 a_{n+1} an+1 为 j j j,则 a n + 2 = s u m + 2 − j a_{n+2}=sum+2-j an+2=sum+2−j。仿照 m = n − 1 m=n-1 m=n−1 的求法,此时树的数量为
( n + 2 − 2 ) ! ∏ i = 1 n + 2 ( a i − 1 ) ! = n ! ( ∏ i = 1 n ( a i − 1 ) ! ) ( j − 1 ) ! ( s u m − j + 1 ) ! \cfrac{(n+2-2)!}{\prod^{n+2}_{i=1}(a_i-1)!}=\cfrac{n!}{\big(\prod^n_{i=1}(a_i-1)!\big)(j-1)!(sum-j+1)!} ∏i=1n+2(ai−1)!(n+2−2)!=(∏i=1n(ai−1)!)(j−1)!(sum−j+1)!n!
但我们需要保证 n + 1 n+1 n+1 与 n + 2 n+2 n+2 在树中无连边(即两个环没有公共点,否则就变成一个环了)。对于有连边的情况,我们按照 m = n m=n m=n 建出 n + 1 n+1 n+1 个点 n n n 条边的树来,则这棵树的 a n + 1 = s u m a_{n+1}=sum an+1=sum,树的数量即为
( n − 1 ) ! ( ∏ i = 1 n ( a i − 1 ) ! ) ( 2 n − ( ∑ i = 1 n a i ) − 1 ) ! = ( n − 1 ) ! ( ∏ i = 1 n ( a i − 1 ) ! ) ( s u m − 1 ) ! \cfrac{(n-1)!}{\big(\prod^n_{i=1}(a_i-1)!\big)\big(2n-(\sum^n_{i=1}a_i)-1\big)!}=\cfrac{(n-1)!}{\big(\prod^n_{i=1}(a_i-1)!\big)(sum-1)!} (∏i=1n(ai−1)!)(2n−(∑i=1nai)−1)!(n−1)!=(∏i=1n(ai−1)!)(sum−1)!(n−1)!
而这样的树每个都有 C s u m j − 1 = s u m ! ( s u m − j + 1 ) ! ( j − 1 ) ! C^{j-1}_{sum}=\cfrac{sum!}{(sum-j+1)!(j-1)!} Csumj−1=(sum−j+1)!(j−1)!sum! 种,则所有不合法的树的数量为
( n − 1 ) ! ( ∏ i = 1 n ( a i − 1 ) ! ) ( s u m − 1 ) ! × s u m ! ( s u m − j + 1 ) ! ( j − 1 ) ! \cfrac{(n-1)!}{\big(\prod^n_{i=1}(a_i-1)!\big)(sum-1)!}\times\cfrac{sum!}{(sum-j+1)!(j-1)!} (∏i=1n(ai−1)!)(sum−1)!(n−1)!×(sum−j+1)!(j−1)!sum!
化简得
( n − 1 ) ! s u m ( ∏ i = 1 n ( a i − 1 ) ! ) ( j − 1 ) ! ( s u m − j + 1 ) ! \cfrac{(n-1)!sum}{\big(\prod^n_{i=1}(a_i-1)!\big)(j-1)!(sum-j+1)!} (∏i=1n(ai−1)!)(j−1)!(sum−j+1)!(n−1)!sum
使用容斥,所有合法的树的数量等于所有减去不合法,即为
( n − 1 ) ! ( n − s u m ) ( ∏ i = 1 n ( a i − 1 ) ! ) ( j − 1 ) ! ( s u m − j + 1 ) ! \cfrac{(n-1)!(n-sum)}{\big(\prod^n_{i=1}(a_i-1)!\big)(j-1)!(sum-j+1)!} (∏i=1n(ai−1)!)(j−1)!(sum−j+1)!(n−1)!(n−sum)
同理,还要算上两个环的排列方案共 ( j − 1 ) ! ( s u m − j + 1 ) ! 4 \cfrac{(j-1)!(sum-j+1)!}{4} 4(j−1)!(sum−j+1)!,则答案为
( n − 1 ) ! ( n − s u m ) ( ∏ i = 1 n ( a i − 1 ) ! ) ( j − 1 ) ! ( s u m − j + 1 ) ! × ( j − 1 ) ! ( s u m − j + 1 ) ! 4 \cfrac{(n-1)!(n-sum)}{\big(\prod^n_{i=1}(a_i-1)!\big)(j-1)!(sum-j+1)!}\times\cfrac{(j-1)!(sum-j+1)!}{4} (∏i=1n(ai−1)!)(j−1)!(sum−j+1)!(n−1)!(n−sum)×4(j−1)!(sum−j+1)!
化简得
( n − 1 ) ! ( n − s u m ) 4 ∏ i = 1 n ( a i − 1 ) ! \cfrac{(n-1)!(n-sum)}{4\prod^{n}_{i=1}(a_i-1)!} 4∏i=1n(ai−1)!(n−1)!(n−sum)
最终我们发现对于不同的 j j j 最终答案相同,一共枚举了 s u m + 2 − 3 − 3 + 1 = s u m − 3 sum+2-3-3+1=sum-3 sum+2−3−3+1=sum−3 次,考虑到两个环位置可以调换但属同一种情况,所以答案要除以二,即为
( n − 1 ) ! ( n − s u m ) ( s u m − 3 ) 8 ∏ i = 1 n ( a i − 1 ) ! \cfrac{(n-1)!(n-sum)(sum-3)}{8\prod^{n}_{i=1}(a_i-1)!} 8∏i=1n(ai−1)!(n−1)!(n−sum)(sum−3)
- 两个环有公共边
将这两个挨在一起的环看作一个点,构造一个与 m = n m=n m=n 时的树,树的数量也相同。现在考虑这挨在一起的两个环的方案。我们可以将这两个环拆成类似韦恩图的样子,分为左边环独有部分、左右环公用部分、右边环都有部分三条链。因为要有公共边,所以有两条及以上的链中边数不大于 1 1 1 显然不合法。而这三条链头尾都是相同的。
因为环挨在一起,所以这两个环上节点一共有 s u m = 2 n − ∑ i = 1 n a i sum=2n-\sum^n_{i=1}a_i sum=2n−∑i=1nai 个点。我们先从 s u m sum sum 中挑两个点出来,之后的每个点都选择两个位置放在中间,不合法数量(即之后的所有点全都放在一条链上)即为 3 ( s u m − 2 ) ! 3(sum-2)! 3(sum−2)!;而放入点的顺序并不影响最终答案。故答案为
s u m ( s u m − 1 ) 2 × s u m ! 2 − 3 ( s u m − 2 ) ! 3 ! = s u m ! 24 ( s u m ( s u m − 1 ) − 6 ) = s u m ! 24 ( s u m + 2 ) ( s u m − 3 ) \begin{aligned}\cfrac{sum(sum-1)}{2}\times\cfrac{\frac{sum!}{2}-3(sum-2)!}{3!}&=\cfrac{sum!}{24}(sum(sum-1)-6)\\&=\cfrac{sum!}{24}(sum+2)(sum-3)\end{aligned} 2sum(sum−1)×3!2sum!−3(sum−2)!=24sum!(sum(sum−1)−6)=24sum!(sum+2)(sum−3)
再乘上树的数量,答案为
s u m ( s u m + 2 ) ( s u m − 3 ) ( n − 1 ) ! 24 ∏ i = 1 n ( a i − 1 ) ! \cfrac{sum(sum+2)(sum-3)(n-1)!}{24\prod^n_{i=1}(a_i-1)!} 24∏i=1n(ai−1)!sum(sum+2)(sum−3)(n−1)!
两种情况分别计算,最后相加即可。复杂度 O ( n ) O(n) O(n)。
实现
预处理出阶乘,逆元用快速幂计算,三种情况分别处理即可。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 1e6 + 5;
const int P = 998244353;
int n,m; ll a[maxn]; ll pro[maxn];
ll inv(ll x) {int y = P - 2; ll res = 1;while (y) {if (y & 1) res = (res * x) % P;x = (x * x) % P, y >>= 1;}return res;
}
int main() {pro[0] = pro[1] = 1;for (ll i = 2;i <= maxn - 5;i ++) pro[i] = (pro[i - 1] * i) % P;scanf("%d%d",&n,&m);if (m == n - 1) { // 1ll tmp = 1;for (int i = 1;i <= n;i ++)scanf("%lld",&a[i]),tmp = (tmp * pro[a[i] - 1]) % P;printf("%lld",pro[n - 2] * inv(tmp) % P);} else if (m == n) { // 2ll tmp = 2;for (int i = 1;i <= n;i ++)scanf("%lld",&a[i]),tmp = (tmp * pro[a[i] - 1]) % P;printf("%lld",pro[n - 1] * inv(tmp) % P);} else { // 3ll sum = n * 2 % P, tmp = 1;for (int i = 1;i <= n;i ++)scanf("%lld",&a[i]),sum -= a[i], tmp = (tmp * pro[a[i] - 1]) % P;ll ans1 = ((pro[n - 1] * (n - sum) % P) * (sum - 3) % P) * inv(tmp * 8 % P) % P;ll ans2 = ((((pro[n - 1] * (sum + 2) % P) * (sum - 3) % P) * sum) % P) * inv(tmp * 24 % P) % P;printf("%lld",(ans1 + ans2) % P);}return 0;
}