bzoj 2560串珠子(状压dp+容斥)

题目链接

题意

有$n$个点,任意两点$i$,$j$之间有$C$$i,j$种连接方式,求将$n$个点连通的方案数。$n<=16$

题解

  • 考虑用所有连接方案数减去不连通的方案数
  • 因为$n<=16$,所以可以状压dp
  • 设$g[i]$为点集为$i$时的无向图个数,$f[i]$为点集为$i$时的无向连通图的个数.
  • 先预处理出所有$g[i]$的值,显然$g[i]=\prod_{j \in i, k \in i, j < k}(c[j][k]+1)$
  • $f[i]$就等于$g[i]$减去包含$i$的最低位的$i$的真子集的无向连通图个数$f[j]$与剩余点集$g[i - j]$的乘积,这样减就能保证不会重复。
  • 即$f[i]=g[i]-\sum_{j \subsetneqq i,(i\&(-i)) \in j}f[j]*g[i - j]$,时间复杂度为$O(3^n+2^n*n^2)$
  • 查看代码
    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int maxn = 20;
    const int mod = 1e9+7;
    ll qpow(ll a,ll b){ll res=1;for(;b;b>>=1){if(b&1)res=res*a%mod;a=a*a%mod;}return res;}
    ll a[maxn][maxn];
    ll g[1<<17],f[1<<17];
    int main()
    {
    #ifndef ONLINE_JUDGE
        freopen("simple.in", "r", stdin);
        freopen("simple.out", "w", stdout);
    #endif
        int n;
        scanf("%d",&n);
        int mx = 1<<n;
        for(int i = 0;i < n;++i){
            for(int j = 0;j < n;++j)scanf("%lld",&a[i][j]);
        }
        for(int i = 1;i < mx;++i){
            g[i]=1;
            for(int j = 0;j < n;++j){
                for(int k = 0;k < j;++k){
                    if(((i>>j)&1)&&((i>>k)&1)){
                        g[i]=g[i]*(a[j][k]+1)%mod;
                    }
                }
            }
            ll tmp = 0;
            for(int j = i&(i-1);j;j = i&(j-1)){
                if(j&(i&-i))
                tmp=(f[j]*g[i^j]+tmp)%mod;
            }
            f[i]=(g[i]-tmp+mod)%mod;
        }
        cout<<f[mx-1]<<endl;
        return 0;
    }
    
posted @ 2020-05-01 14:53  tryatry  阅读(1194)  评论(0编辑  收藏  举报