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

0%

「ZJOI 2017」仙人掌(组合计数)

题目大意

「ZJOI 2017」仙人掌(Luogu 3687)

给定一个 $n$ 个点 $m$ 条边的无向联通图,你要在图上加若干条不重复的边(可以加 $0$ 条),使得图变成一棵仙人掌。求加边方案数量$\bmod 998244353$ 的结果。

数据范围:$n \le 5 \times 10^5, m \le 10^6$。

思路分析

搞不懂为什么好多题解都要用树形 DP。

首先如果一个图不是仙人掌,那么它加边后也不可能变成一个仙人掌,方案数为 $0$。

对于一个仙人掌,发现加边的两个端点形成的路径上不能经过任何一个环,否则在环上的那条边就会被新加上的那条边形成的新环包含,也就是被两个环包含。所以环不对答案产生影响,我们事先把环上的所有边删去,然后对于留下的每一棵树,计算它单独的方案数,最后乘起来即可。

对于一棵树,进行一个转化:在两个结点之间加一条边转化成覆盖在树上两个结点形成的路径。于是我们发现这条路径包含的边数肯定 $\ge 2$,并且选出的路径的边不能有相交。为了使算法更加简洁,我们将没有被覆盖的边视为被长度为 $1$ 的路径覆盖了。于是问题就转化成了求树上选出一些路径恰好覆盖所有边的方案数。

对于每个结点,我们考虑它的邻边配对的情况。邻边可以单个一组,也就是作为一条路径的端点;或者可以两两配对,也就是作为一条路径中间的某处。记有 $n$ 条邻边的方案数为 $f(n)$,考虑 $f$ 的递推公式。我们新加入 $n$ 号邻边时,对于第一种情况,剩下的邻边随便配,故 $f(n) \leftarrow f(n) + f(n - 1)$。对于第二种情况,新加入的边要和以前的某个边配对,剩下的 $n - 2$ 条边随便配,故 $f(n) \leftarrow f(n) + (n - 1) \times f(n - 2)$。

我们发现对于每个结点方案数不互相影响。所以总方案数为 $\prod f(\text{deg}(i))$。

至此问题得到圆满解决。时间复杂度 $O(n + m)$。

代码实现

如何判断仙人掌呢?我们搞出一棵 DFS 树,然后对于一条返祖边,我们把它两个端点在 DFS 树上的路径 $+1$。最后如果每条边的权值都 $\le 1$,则图是仙人掌。而权值为 $1$ 的边是环上的边。

1
#include <cstdio>
2
#include <vector>
3
using namespace std;
4
5
const int maxn = 5e5, mod = 998244353;
6
int T, n, m, f[maxn + 3], deg[maxn + 3], dep[maxn + 3], cnt[maxn + 3];
7
vector<int> G[maxn + 3];
8
bool vis[maxn + 3];
9
10
void clear() {
11
	for (int i = 1; i <= n; i++) {
12
		G[i].clear(), deg[i] = 0;
13
		vis[i] = false, cnt[i] = 0;
14
	}
15
}
16
17
void add(int u, int v) {
18
	G[u].push_back(v);
19
}
20
21
void dfs1(int u, int pa = 0) {
22
	vis[u] = true;
23
	for (int i = 0, v; i < G[u].size(); i++) {
24
		v = G[u][i];
25
		if (v == pa) continue;
26
		if (!vis[v]) {
27
			dep[v] = dep[u] + 1;
28
			dfs1(v, u);
29
			cnt[u] += cnt[v];
30
		} else if (dep[u] > dep[v]) {
31
			cnt[u]++, cnt[v]--;
32
		}
33
	}
34
}
35
36
void dfs2(int u) {
37
	vis[u] = true;
38
	for (int i = 0, v; i < G[u].size(); i++) {
39
		v = G[u][i];
40
		if (vis[v]) continue;
41
		dfs2(v);
42
		if (!cnt[v]) {
43
			deg[u]++, deg[v]++;
44
		}
45
	}
46
}
47
48
int main() {
49
	f[0] = f[1] = 1;
50
	for (int i = 2; i <= maxn; i++) {
51
		f[i] = (f[i - 1] + 1ll * f[i - 2] * (i - 1)) % mod;
52
	}
53
	scanf("%d", &T);
54
	while (T--) {
55
		clear();
56
		scanf("%d %d", &n, &m);
57
		for (int i = 1, u, v; i <= m; i++) {
58
			scanf("%d %d", &u, &v);
59
			add(u, v), add(v, u);
60
		}
61
		dfs1(1);
62
		bool flag = false;
63
		for (int i = 1; i <= n; i++) if (cnt[i] > 1) {
64
			flag = true;
65
		}
66
		if (flag) {
67
			puts("0");
68
			continue;
69
		}
70
		for (int i = 1; i <= n; i++) {
71
			vis[i] = false;
72
		}
73
		dfs2(1);
74
		int ans = 1;
75
		for (int i = 1; i <= n; i++) {
76
			ans = 1ll * ans * f[deg[i]] % mod;
77
		}
78
		printf("%d\n", ans);
79
	}
80
	return 0;
81
}