ABC253EX (fzt子集计数+矩阵树定理)

本文介绍了一种计算给定无向图中边集大小为k的森林数量的高效算法,利用矩阵树定理在O(2^nn^3)时间内求解f(k),并通过递推公式g(S,i)探讨了生成森林的不同策略,包括一个trick来避免重复计数。
摘要由CSDN通过智能技术生成

题意:给n个点m条边的无向图,定义f(k)为边集大小为k的森林数量。对k从1~n-1,求所有的f(k)。n<=14,m<=500

Soltion:

设f(S)表示,选定点集S能构成的生成树数量。

设g(S,i)表示,选定点集S能且用了i条边所构成的森林的数量。

那么答案就是求,对于所有的1~n-1,求g(U,i),U表示点的全集。

对于f(S)的计算,用矩阵树定理可以在 O ( 2 n n 3 ) O(2^nn^3) O(2nn3)内计算。具体来说,就是枚举所有状态S,然后算。

对于g(S)的计算,有转移方程
g ( S , i ) = ∑ T ∈ S f ( T ) ∗ g ( S / T , i − ( ∣ T ∣ − 1 ) ) g(S,i)=\sum_{T∈S}f(T)*g(S/T,i-(|T|-1)) g(S,i)=TSf(T)g(S/T,i(T1)),枚举S的子集是树,同时枚举i,这里用递推好写一点,最里层枚举i,其次枚举子集。
复杂度O(n3^n)。

一个trick,就是算g(S,i)时候需要钦定一个点v不动,对于子集T如果T不包含v的话,则不算上T的贡献,不知道什么原理。。。不这样的话会算重。。。有大佬知道的话,麻烦留言区教教^ - ^。
另一题ABC213G也有同样的trick。
不过有另一种方程,具体是对于集合S,f(S)是点集S连通的方案,枚举其子集T,然后直接f(S) = ∑f(T)g(S/T)这样,数,钦定其连通的子图,然后剩下部分任意,这样保证了T和S/T不连通。因此不会算重。

#define ll long long
#define Maxn 305
const int N=1e3+5;
const int mod=998244353;
ll powM(ll a,int t=mod-2){
    ll ans=1;
    while(t){
        if (t&1)ans=ans*a%mod;
        a=a*a%mod;t>>=1;
    }
    return ans;
}
int n,m;
int a[N][N],mp[N][N];
int f[1<<14],g[20][1<<14];
ll det(ll a[][N],int n){
    ll ans=1;bool tr=0;
    for(int j=2;j<=n;j++){
        for(int i=j;i<=n;i++)
             if (a[i][j]){
                if (i!=j){
                    swap(a[i],a[j]);
                    tr=!tr;
                }
                break;
                }
            if (a[j][j]==0)return 0;
    ans=ans*a[j][j]%mod;
    ll t=powM(a[j][j]);
    for (int k=j;k<=n;k++)a[j][k]=t*a[j][k]%mod;
    for (int i=j+1;i<=n;i++){
      t=mod-a[i][j];
      for (int k=j;k<=n;k++)
        a[i][k]=(a[i][k]+t*a[j][k])%mod;
    }
    }
    return tr?(mod-ans)%mod : ans;
}
bool pd(int i,int j){
    return 0<=i&&i<n&&0<=j&&j<m;
}
int fac[N],ni_f[N];
ll qsm(int a,int b){
    ll ans=1,tmp=a;
    while( b ) {
        if( b&1 ) ans = ans * tmp%mod;
        tmp = tmp * tmp%mod;
        b>>=1;
    }
    return ans;
}
void ini(){
    int maxn = 1e3;
    fac[0]=1;
    _for(i,1,maxn) fac[i] =  (fac[i-1] * i)%mod;
    ni_f[maxn] = qsm(fac[maxn],mod-2);
    _rep(i,maxn-1,0) ni_f[i] = ni_f[i+1] * (i+1)%mod;
}
signed main(){
#ifndef ONLINE_JUDGE
    freopen("in.txt", "r", stdin);
#endif  
    ini();
    cin>>n>>m;
    _for(i,1,m){
        int u,v;cin>>u>>v;
        if( u > v ) swap(u,v);
        mp[v][u]++;
    }
    for(int S = 1 ;S<(1<<n) ;S++){
        _for(i,1,n) _for(j,1,n) a[i][j] = 0;
        int siz = 0;
        int id[n+1];
        for(int j=1 ;j<=n ;j++){
            if( S>>(j-1)&1 ){
                id[j] = ++siz;
                _for(k,1,j-1){
                    if( mp[j][k] && (S>>(k-1))&1 ){
                        int u = id[j];
                        int v = id[k];
                        int w = mp[j][k];
                        a[u][u] = (a[u][u] + w)%mod;
                        a[v][v] = (a[v][v] + w)%mod;
                        a[u][v] = (a[u][v] + mod - w)%mod;
                        a[v][u] = (a[v][u] + mod - w)%mod;
                    }
                }
            }
        }
        f[S] = det(a,siz);
    }
    g[0][0] = 1;
    for (int i = 1; i < 1 << n; i++)
        for (int sub = i; sub > 0; sub = (sub - 1) & i)
            for (int k = 0; k < n;k++){
                if( ~sub&(i&(-i)) ) continue;
                int j = k + __builtin_popcount(sub) - 1;
                if (j < n){
                    g[j][i] = (g[j][i] + f[sub] * g[k][sub ^ i] % mod) % mod;
                }
            }
    int all = (1 << n) - 1;
    for (int i = 1; i < n; i++){
        int ans = g[i][all] * fac[i] %  mod * powM(powM(m, i)) % mod;
        cout<<ans<<endl;
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值