题解:P9535 [YsOI2023] 连通图计数

题意

求:在所有 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 n1mn+1

思路

m = n − 1 , m = n , m = n + 1 m=n-1,m=n,m=n+1 m=n1,m=n,m=n+1​ 三种情况进行分类讨论。

对于 m = n − 1 m=n-1 m=n1,显然是一棵树,每个 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(ai1)!(n2)!
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=2ni=1nai
根据上一种情况,再乘上环上点的排列方案 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(ai1)!)(2n(i=1nai)1)!(n1)!×2(2n(i=1nai)1)!=2i=1n(ai1)!(n1)!
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=2ni=1nai+2
我们设 2 n − ∑ i = 1 n a i = s u m 2n-\sum^n_{i=1}a_i=sum 2ni=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+2j。仿照 m = n − 1 m=n-1 m=n1 的求法,此时树的数量为
( 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(ai1)!(n+22)!=(i=1n(ai1)!)(j1)!(sumj+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(ai1)!)(2n(i=1nai)1)!(n1)!=(i=1n(ai1)!)(sum1)!(n1)!
而这样的树每个都有 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)!} Csumj1=(sumj+1)!(j1)!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(ai1)!)(sum1)!(n1)!×(sumj+1)!(j1)!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(ai1)!)(j1)!(sumj+1)!(n1)!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(ai1)!)(j1)!(sumj+1)!(n1)!(nsum)
同理,还要算上两个环的排列方案共 ( j − 1 ) ! ( s u m − j + 1 ) ! 4 \cfrac{(j-1)!(sum-j+1)!}{4} 4(j1)!(sumj+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(ai1)!)(j1)!(sumj+1)!(n1)!(nsum)×4(j1)!(sumj+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)!} 4i=1n(ai1)!(n1)!(nsum)
最终我们发现对于不同的 j j j 最终答案相同,一共枚举了 s u m + 2 − 3 − 3 + 1 = s u m − 3 sum+2-3-3+1=sum-3 sum+233+1=sum3 次,考虑到两个环位置可以调换但属同一种情况,所以答案要除以二,即为
( 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)!} 8i=1n(ai1)!(n1)!(nsum)(sum3)

  • 两个环有公共边

将这两个挨在一起的环看作一个点,构造一个与 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=2ni=1nai 个点。我们先从 s u m sum sum 中挑两个点出来,之后的每个点都选择两个位置放在中间,不合法数量(即之后的所有点全都放在一条链上)即为 3 ( s u m − 2 ) ! 3(sum-2)! 3(sum2)!​;而放入点的顺序并不影响最终答案。故答案为
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(sum1)×3!2sum!3(sum2)!=24sum!(sum(sum1)6)=24sum!(sum+2)(sum3)
再乘上树的数量,答案为
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)!} 24i=1n(ai1)!sum(sum+2)(sum3)(n1)!
两种情况分别计算,最后相加即可。复杂度 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) { // 1
        ll 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) { // 2
        ll 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 { // 3
        ll 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;
}
  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值