【題解】[SHOI2016]黑暗前的幻想鄉
少有的自己逐漸發現了容斥思路的題……
題目大意:要求原無向圖的生成樹個數,滿足每條邊被建立的公司不同。
\(\text{Solution:}\)
觀察到了我們有 \(n-1\) 個公司,所以在題目的限制下,滿足條件的生成樹恰好所有公司都參與。所以我們不需要考慮每條邊了,直接考慮:對當前情況下 有 \(x\) 個公司參與的生成樹個數 。這樣只要我們求出對應 \(n-1\) 情況下的答案,那它直接就是答案了。
那麼回到原來的問題,為什麼直接忽略公司對原圖跑 Matrix-Tree 定理不對呢?這樣的答案中 包含了許多 \(n-1\) 個公司中的子集。 那麼思路就變成了:如何把這些不合法的子集去掉呢?結合 \(n\leq 17\)
仔細思考一下,我們想要列舉一些公司使得它們 每一個 都要參與到生成樹中。但實際上這個問題也是困難的,那不妨先擱置一下,來想一想容斥的想法:
顯然我們可以用總的減去算重的:列舉參加了多少公司,則:
\[Ans=\sum_{n-1}f_i-\sum_{n-2}f_i+\sum_{n-3}f_i... \]容斥係數就和列舉到的公司數奇偶性有關係。
那麼,這個式子是對於強制了多少公司都參與的情況下成立的,剛剛我們說即使這樣算,算到的結果也包含了對應這些公司的子集。那麼怎麼算呢?
其實按照上面這個式子上去算也是對的,因為在每一步加減的過程中,比當前列舉到的更小的子集都被抵消掉了,不理解的話可以手推一下,由於恰好抵消,所以我們直接這樣列舉容斥就是對的。
於是我們就可以套用矩陣樹定理了。注意到有重邊的情況,由於這種重邊一定來自於兩個不同的公司,所以我們不能把它刪掉,直接加進去就是對的。
順便再提一句矩陣樹定理:基爾霍夫矩陣是用 度數矩陣 減去 鄰接矩陣,求出的結果是:所有生成樹邊權之積的和。
那麼求方案數的時候,只需要令乘積為 \(1\to\) 令邊權為 \(1\) 即可。
複雜度 \(O(2^n\cdot n^3),\) 跑的還是蠻快的。
#include<bits/stdc++.h> using namespace std; const int mod=1e9+7; const int N=20; inline int Add(int x,int y){return (x+y)%mod;} inline int Mul(int x,int y){return 1ll*x*y%mod;} inline int Dec(int x,int y){return (x-y+mod)%mod;} inline int Max(int x,int y){return x>y?x:y;} inline int Min(int x,int y){return x<y?x:y;} inline int Abs(int x){if(x<0)x=-x;return x;} inline int qpow(int x,int y){ int res=1; while(y){ if(y&1)res=Mul(res,x); x=Mul(x,x);y>>=1; } return res; } inline int getinv(int x){return qpow(x,mod-2);} inline int read(){ int s=0; char ch=getchar(); while(!isdigit(ch))ch=getchar(); while(isdigit(ch)){ s=s*10+ch-'0'; ch=getchar(); } return s; } typedef pair<int,int> pr; #define fi first #define se second #define mp make_pair int n,m[N]; vector<pr>edge[N]; struct Matrix{ int a[N][N]; Matrix(){memset(a,0,sizeof a);} int MatrixTree(int L){ int res=1; for(int i=L;i<=n;++i){ int pos=i; for(int j=i+1;j<=n;++j) if(a[pos][i]<a[j][i]) pos=j; if(i!=pos)res=Mul(res,mod-1),swap(a[i],a[pos]); int div=getinv(a[i][i]); res=Mul(res,a[i][i]); for(int j=i;j<=n;++j)a[i][j]=Mul(a[i][j],div); for(int j=i+1;j<=n;++j){ div=a[j][i]; for(int k=i;k<=n;++k)a[j][k]=Dec(a[j][k],Mul(div,a[i][k])); } } for(int i=L;i<=n;++i)res=Mul(res,a[i][i]); return res; } }; int f[1<<N]; Matrix GetMatrix(int state){ Matrix A; for(int i=0;i<n-1;++i){ if(!(state>>i&1))continue; for(auto j:edge[i]){ int u=j.fi; int v=j.se; A.a[u][u]=Add(A.a[u][u],1); A.a[v][v]=Add(A.a[v][v],1); A.a[u][v]=Dec(A.a[u][v],1); A.a[v][u]=Dec(A.a[v][u],1); } } return A; } inline int lowbit(int x){return x&(-x);} inline int popcount(int x){ int res=0; while(x){ res++; x-=lowbit(x); } return res; } int sum[N],Ans; int main(){ n=read(); for(int i=0;i<n-1;++i){ m[i]=read(); for(int j=0;j<m[i];++j){ int u=read(),v=read(); edge[i].push_back(mp(u,v)); } } for(int i=0;i<(1<<(n-1));++i){ Matrix A=GetMatrix(i); f[i]=A.MatrixTree(2); } for(int i=0;i<(1<<(n-1));++i){ int num=popcount(i); sum[num]=Add(sum[num],f[i]); } int opt=(n-1)&1; for(int i=n-1;i>=1;--i){ int op=i&1; if(op==opt)Ans=Add(Ans,sum[i]); else Ans=Dec(Ans,sum[i]); } printf("%d\n",Ans); return 0; }