1. 程式人生 > 實用技巧 >[洛谷P3349] ZJOI2016 小星星

[洛谷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;
}