Live2D

题解 小星星

题目传送门

题目大意

给出 \(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;
}
posted @ 2020-11-20 09:09  Dark_Romance  阅读(88)  评论(0编辑  收藏  举报