[洛谷P3349] ZJOI2016 小星星
問題描述
小Y是一個心靈手巧的女孩子,她喜歡手工製作一些小飾品。她有n顆小星星,用m條彩色的細線串了起來,每條細線連著兩顆小星星。
有一天她發現,她的飾品被破壞了,很多細線都被拆掉了。這個飾品只剩下了n-1條細線,但通過這些細線,這顆小星星還是被串在一起,也就是這些小星星通過這些細線形成了樹。小Y找到了這個飾品的設計圖紙,她想知道現在飾品中的小星星對應著原來圖紙上的哪些小星星。如果現在飾品中兩顆小星星有細線相連,那麼要求對應的小星星原來的圖紙上也有細線相連。小Y想知道有多少種可能的對應方式。
只有你告訴了她正確的答案,她才會把小飾品做為禮物送給你呢。
輸入格式
第一行包含個2正整數n,m,表示原來的飾品中小星星的個數和細線的條數。接下來m行,每行包含2個正整數u,v,表示原來的飾品中小星星u和v通過細線連了起來。這裡的小星星從1開始標號。保證u≠v,且每對小星星之間最多隻有一條細線相連。接下來n-1行,每行包含個2正整數u,v,表示現在的飾品中小星星u和v通過細線連了起來。保證這些小星星通過細線可以串在一起。n<=17,m<=n*(n-1)/2
輸出格式
輸出共1行,包含一個整數表示可能的對應方式的數量。如果不存在可行的對應方式則輸出0。
樣例輸入
4 3
1 2
1 3
1 4
4 1
4 2
4 3
樣例輸出
6
解析
首先考慮暴力中的暴力:直接列舉樹上的點對應的原圖上點的序號,再驗證是否可行。
顯然這個暴力沒有多大的意義,但它可以作為正解的啟發。設 \(f_{i,j}\) 表示考慮 \(i\) 的子樹,且 \(i\) 對應原圖上的 \(j\) 的方案數。那麼 \(i\) 的所有子節點必須在原圖上對應與 \(j\) 相連。我們可以得到如下轉移方程:
\[f_{u,i}=\prod_{v\in u}\ \ \sum_{j=1}^n f_{v,j}\times g_{i,j} \]
其中 \(g\) 是原圖的鄰接矩陣。但是,這樣做沒有考慮不能重複對映的條件。最後對映集合中的點可能少於 \(n\) 個。那接下來我們可以強制只能對映到 \(n-1\) 個點,然後用總方案數減去這裡計算得到的方案數。在這個過程中,我們又會遇到一樣的問題,我們要把多減的加回來。因此通過容斥原理,我們列舉對映集合,每次從圖上刪除不在對映集合裡的點,然後 \(O(n^3)\) DP即可。
程式碼
#include <iostream> #include <cstdio> #define N 22 using namespace std; int head[N],ver[N*2],nxt[N*2],l; int n,m,i,j; long long f[N][N],ans; bool g[N][N],g1[N][N],vis[N]; int read() { char c=getchar(); int w=0; while(c<'0'||c>'9') c=getchar(); while(c<='9'&&c>='0'){ w=w*10+c-'0'; c=getchar(); } return w; } void insert(int x,int y) { l++; ver[l]=y; nxt[l]=head[x]; head[x]=l; } void dp(int x,int pre) { for(int i=1;i<=n;i++) f[x][i]=1; for(int i=head[x];i;i=nxt[i]){ int y=ver[i]; if(y!=pre){ dp(y,x); for(int j=1;j<=n;j++){ long long tmp=0; for(int k=1;k<=n;k++){ if(g[j][k]) tmp+=f[y][k]; } f[x][j]*=tmp; } } } } void dfs(int x) { if(x==n+1){ int cnt=0; for(int i=1;i<=n;i++){ if(vis[i]){ for(int j=1;j<=n;j++) g[i][j]=g[j][i]=0; cnt++; } } dp(1,0); if(cnt%2==0){ for(int i=1;i<=n;i++) ans+=f[1][i]; } else{ for(int i=1;i<=n;i++) ans-=f[1][i]; } for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++) g[i][j]=g1[i][j]; } return; } dfs(x+1); vis[x]=1;dfs(x+1);vis[x]=0; } int main() { n=read();m=read(); for(i=1;i<=m;i++){ int u=read(),v=read(); g[u][v]=g[v][u]=1; } for(i=1;i<n;i++){ int u=read(),v=read(); insert(u,v);insert(v,u); } for(i=1;i<=n;i++){ for(j=1;j<=n;j++) g1[i][j]=g[i][j]; } dfs(1); printf("%lld\n",ans); return 0; }