1. 程式人生 > 實用技巧 >[SHOI2016] 黑暗前的幻想鄉

[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; }
黑暗前的幻想鄉