1. 程式人生 > 實用技巧 >題解 小星星

題解 小星星

題目傳送門

題目大意

給出 \(n\) 個點 \(m\) 條邊的圖,給出一個樹,問有多少個對映使得樹上的邊在原圖都出現過。

\(n\le 17,m\le n(n-1)/2\)

思路

不難看出 \(\Theta(3^n\times n)\) 的 dp,我們可以設 \(f_{i,j,S}\) 表示以 \(i\) 為根的子樹對映到集合 \(S\),且 \(i\) 點對映到 \(j\) 的方案數。轉移式顯然。

考慮容斥,我們可以設當前集合為 \(S\),那麼我們在 dp 的時候欽定每個點只能對映到 \(S\)。這樣的意思實際上就是消掉對映重複的方案。

時間複雜度 \(\Theta(2^n\times n)\)

\(\texttt{Code}\)

#include <bits/stdc++.h>
using namespace std;

#define Int register int
#define int long long
#define MAXN 25

template <typename T> void read (T &x){char c = getchar ();x = 0;int f = 1;while (c < '0' || c > '9') f = (c == '-' ? -1 : 1),c = getchar ();while (c >= '0' && c <= '9') x = x * 10 + c - '0',c = getchar ();x *= f;}
template <typename T,typename ... Args> void read (T &x,Args& ... args){read (x),read (args...);}
template <typename T> void write (T x){if (x < 0) x = -x,putchar ('-');if (x > 9) write (x / 10);putchar (x % 10 + '0');}

bool app[MAXN];
int n,m,f[MAXN][MAXN];
vector <int> G[MAXN],E[MAXN];

void dfs (int u,int fa){
	for (Int i = 1;i <= n;++ i) f[u][i] = 1;
	for (Int v : G[u]) if (v ^ fa){
		dfs (v,u);
		for (Int i = 1;i <= n;++ i){
			int s = 0;for (Int k : E[i]) s += f[v][k] * (app[i] & app[k]);
			f[u][i] *= s;
		}
	}
}

signed main(){
	read (n,m);
	for (Int i = 1,u,v;i <= m;++ i) read (u,v),E[u].push_back (v),E[v].push_back (u);
	for (Int i = 2,u,v;i <= n;++ i) read (u,v),G[u].push_back (v),G[v].push_back (u);
	int ans = 0;for (Int S = 1;S < (1 << n);++ S){
		memset (app,0,sizeof (app));int siz = n;
		for (Int i = 1;i <= n;++ i) if (S >> i - 1 & 1) app[i] = 1,siz --;
		dfs (1,0);for (Int i = 1;i <= n;++ i) ans += f[1][i] * (siz & 1 ? -1 : 1);
	}
	write (ans),putchar ('\n');
	return 0;
}