只要你跑的够快,锅就追不上你

0%

「ZJOI 2016」小星星(容斥原理 + 动态规划)

题目大意

「ZJOI 2016」小星星(Luogu 3349)

给定一个 $n$ 个结点 $m$ 条边的无向图和一棵 $n$ 个结点的树。现在要把树中的每个结点不重复,不遗漏地对应到图中的每个结点上,满足对应后形成的树是图的一棵生成树。求合法方案的数量。

数据范围:$n \le 17$。

思路分析

先考虑如果没有不重复,不遗漏这个条件怎么做。相当于给树上的每个结点都染个颜色,满足相邻的两个结点的颜色之间对应有边。记 $\text{dp}(i, j)$ 表示结点 $i$ 染成颜色 $j$ 的方案数,我们只要暴力枚举儿子的颜色即可转移。时间复杂度 $O(n ^ 3)$。

但是现在有 “所有颜色都要用到” 这个条件。考虑最一般的容斥原理。对于上述的 DP,我们记对于颜色集合 $A$ 算出的答案为 $f(A)$。我们只要每次枚举一个颜色的子集 $S$ 不能使用,然后把 $(-1)^{\vert A \vert} \times f(S - A)$ 贡献到答案里就行了,其中 $S$ 是颜色的全集。时间复杂度 $O(2 ^ n \times n ^ 3)$。

代码实现

1
#include <cstdio>
2
#include <cstring>
3
#include <vector>
4
using namespace std;
5
6
typedef long long ll;
7
const int maxn = 18;
8
int n, m, cur_msk;
9
ll dp[maxn + 3][maxn + 3];
10
bool a[maxn + 3][maxn + 3];
11
vector<int> G[maxn + 3];
12
13
void add(int u, int v) {
14
	G[u].push_back(v);
15
}
16
17
int bit_cnt(int x) {
18
	return __builtin_popcount(x);
19
}
20
21
void dfs(int u, int pa = 0) {
22
	for (int i = 1; i <= n; i++) {
23
		dp[u][i] = ~cur_msk >> (n - i) & 1;
24
	}
25
	for (int i = 0, v; i < G[u].size(); i++) {
26
		v = G[u][i];
27
		if (v == pa) continue;
28
		dfs(v, u);
29
		for (int i = 1; i <= n; i++) if (dp[u][i]) {
30
			ll sum = 0;
31
			for (int j = 1; j <= n; j++) {
32
				if (a[i][j]) sum += dp[v][j];
33
			}
34
			dp[u][i] *= sum;
35
		}
36
	}
37
}
38
39
int main() {
40
	scanf("%d %d", &n, &m);
41
	for (int i = 1, u, v; i <= m; i++) {
42
		scanf("%d %d", &u, &v);
43
		a[u][v] = a[v][u] = true;
44
	}
45
	for (int i = 1, u, v; i < n; i++) {
46
		scanf("%d %d", &u, &v);
47
		add(u, v), add(v, u);
48
	}
49
	ll ans = 0, res;
50
	for (int msk = 0; msk < (1 << n) - 1; msk++) {
51
		cur_msk = msk;
52
		dfs(1);
53
		res = 0;
54
		for (int i = 1; i <= n; i++) {
55
			res += dp[1][i];
56
		}
57
		if (~bit_cnt(msk) & 1) {
58
			ans += res;
59
		} else {
60
			ans -= res;
61
		}
62
	}
63
	printf("%lld\n", ans);
64
	return 0;
65
}