[SHOI2016] 黑暗前的幻想乡
题意:
有n个点,你想修n-1条路将这n个点联通。有n-1家建筑公司,每家建筑公司可以修m条边。
你希望让每家建筑公司恰好修一条边。求修路方案数对$10^{9}+7$取模的值。
$n\leq 17,m\leq \frac{n(n-1)}{2}$。
题解:
挺水的一道题。看到“每家恰好修一条边”就想到容斥。
显然总方案数$N=N(随便修)-N(至少一家不修)=N(随便修)-N(钦定一家不修)+N(钦定两家不修)-\cdots$。
那么考虑枚举每家公司是否修的二进制状态,我们只需要把所有为1的公司的边拿出来建图,然后求该图的生成树个数即可。
复杂度$O(2^{n}n^{3})$,我也不知道怎么回事就过了。
套路:
- 形如“恰好/至少/至多k个满足条件”的计数问题$\rightarrow$容斥。
代码:
#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\n",ans); return 0; }