[SHOI2016] 黑暗前的幻想乡

题意:

有n个点,你想修n-1条路将这n个点联通。有n-1家建筑公司,每家建筑公司可以修m条边。

你希望让每家建筑公司恰好修一条边。求修路方案数对$10^{9}+7$取模的值。

$nleq 17,mleq frac{n(n-1)}{2}$。

题解:

挺水的一道题。看到“每家恰好修一条边”就想到容斥。

显然总方案数$N=N(随便修)-N(至少一家不修)=N(随便修)-N(钦定一家不修)+N(钦定两家不修)-cdots$。

那么考虑枚举每家公司是否修的二进制状态,我们只需要把所有为1的公司的边拿出来建图,然后求该图的生成树个数即可。

复杂度$O(2^{n}n^{3})$,我也不知道怎么回事就过了。

套路:

  • 形如“恰好/至少/至多k个满足条件”的计数问题$ ightarrow$容斥。

代码:

#include<bits/stdc++.h>
#define maxn 20
#define maxm 500005
#define inf 0x7fffffff
#define mod 1000000007
#define ll long long
#define rint register ll
#define debug(x) cerr<<#x<<": "<<x<<endl
#define fgx cerr<<"--------------"<<endl
#define dgx cerr<<"=============="<<endl

using namespace std;
struct edge{ll u,v;};
vector<edge> E[maxn];
ll A[maxn][maxn];

inline ll read(){
    ll x=0,f=1; char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    return x*f;
}

inline ll pw(ll a,ll b){ll r=1;while(b)r=(b&1)?r*a%mod:r,a=a*a%mod,b>>=1;return r;}
inline ll inv(ll x){return pw(x,mod-2);}

inline ll Gauss(ll n){
    ll ans=1;
    for(rint j=1;j<=n;j++){
        for(rint i=j;i<=n;i++)
            if(A[i][j]!=0){
                for(rint k=1;k<=n;k++) swap(A[i][k],A[j][k]);
                if(i!=j) ans=ans*(mod-1)%mod; break;
            }
        if(A[j][j]==0) return 0;
        for(rint i=1;i<=n;i++)
            if(i!=j && A[i][j]!=0){
                ll x=A[i][j]*inv(A[j][j])%mod;
                for(rint k=1;k<=n;k++) A[i][k]=(A[i][k]-A[j][k]*x%mod+mod)%mod;
            }
    }
    for(rint i=1;i<=n;i++) ans=ans*A[i][i]%mod;
    return ans;
}

int main(){
    ll n=read(),ans=0;
    for(rint i=1;i<n;i++){
        ll m=read();
        for(rint j=1;j<=m;j++){
            ll u=read(),v=read();
            E[i].push_back((edge){u,v});
        }
    }
    for(rint s=0,a=1;s<(1<<(n-1));s++){
        a=1,memset(A,0,sizeof(A)); 
        for(rint i=1;i<n;i++){
            if(!(s&(1<<i-1))){a=a*(mod-1)%mod;continue;}
            for(rint j=0;j<E[i].size();j++){
                ll u=E[i][j].u,v=E[i][j].v;
                A[u][u]=(A[u][u]+1)%mod,A[v][v]=(A[v][v]+1)%mod;
                A[u][v]=(A[u][v]+mod-1)%mod,A[v][u]=(A[v][u]+mod-1)%mod;
            }
        }
        ans=(ans+a*Gauss(n-1)%mod)%mod;
    }
    printf("%lld
",ans);
    return 0;
}
黑暗前的幻想乡
原文地址:https://www.cnblogs.com/YSFAC/p/13246217.html