题目大意
给定一个
n
个点,
多组数据。
∑n≤5∗105
∑m≤106
解题思路
首先讨论给定的图是树的情况。
(转化一)
要求不能有边存在与两个简单环内相当于我们要加入新的边去覆盖这个树,是的每条边最多被覆盖一次。
由于每条边不一定被覆盖,所以比较麻烦。注意到题目的一个性质,不能存在重边,那么意味着我们可以将原来构出仙人掌中不存在环内的边视为向原来树中的父亲连了一条边,这个与原问题是等价的。这样就有了每条树边都要被覆盖一次的限制。
(转化二)
问题就转化成了有多少种覆盖方案使得树上每条边恰好被覆盖一次。
这就很好做了,设
f[i]
表示做完以
i
为根的子树,且没有路径可以向上扩展。
转移就很显然了。
h[0]=h[1]=1
h[i]=h[i−1]+h[i−2]∗(i−1)(i>2)
f[i]=(∏j∈son[i]g[j])∗h[|son[i]|]
g[i]=f[i]+(∏j∈son[i]g[j])∗h[|son[i]|−1]∗|son[i]|
(分
i
<script type="math/tex" id="MathJax-Element-585">i</script>节点是否为向上覆盖边的端点讨论)
对于原图不是树的情况可以先把原图不是仙人掌的情况判掉,答案肯定是0。由于原图的环是不能被任何边覆盖的,所以直接把环上的边删掉,转化成若干棵树即可。
程序
//YxuanwKeith
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int MAXN = 5e5 + 5, MAXM = 1e6 + 5;
const int Mo = 998244353;
struct Path {
int u, v;
} path[MAXN];
int n, m, tim, bel[MAXN], deep[MAXN], fa[MAXN], f[MAXN], g[MAXN], h[MAXN], tag[MAXN];
int tot, Last[MAXN], Next[MAXM * 2], Go[MAXM * 2];
bool flag[MAXN];
void link(int u, int v) {
Next[++ tot] = Last[u], Last[u] = tot, Go[tot] = v;
}
void dfs(int now, int pre) {
flag[now] = 1, fa[now] = pre;
deep[now] = deep[pre] + 1;
bel[++ tim] = now;
for (int p = Last[now]; p; p = Next[p])
if (!flag[Go[p]]) dfs(Go[p], now);
}
bool remake() {
dfs(1, 0);
for (int i = 1; i <= n; i ++) flag[i] = 0;
for (int i = 1; i <= m; i ++) {
int u = path[i].u, v = path[i].v;
if (deep[u] < deep[v]) swap(u, v);
if (fa[u] == v) continue;
tag[u] ++, tag[v] --;
}
for (int i = n; i; i --) {
int now = bel[i];
for (int p = Last[now]; p; p = Next[p]) {
int v = Go[p];
if (fa[v] != now) continue;
tag[now] += tag[v];
}
if (!tag[now]) flag[now] = 1;
if (tag[now] > 1) return printf("0\n"), 0;
}
for (int i = 1; i <= n; i ++) Last[i] = 0;
tot = 0;
for (int i = 1; i <= n; i ++)
if (flag[i] && fa[i]) link(i, fa[i]), link(fa[i], i);
return 1;
}
void dp(int now, int pre) {
flag[now] = 1;
f[now] = 1, g[now] = 0;
int num = 0;
for (int p = Last[now]; p; p = Next[p]) {
int v = Go[p];
if (v == pre) continue;
dp(v, now);
f[now] = 1ll * f[now] * g[v] % Mo;
num ++;
}
g[now] = (1ll * f[now] * h[num] % Mo + 1ll * f[now] * h[num - 1] % Mo * num % Mo) % Mo;
f[now] = 1ll * f[now] * h[num] % Mo;
}
void solve() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++) Last[i] = tag[i] = flag[i] = 0;
h[0] = h[1] = 1;
for (int i = 2; i <= n; i ++)
h[i] = (h[i - 1] + 1ll * h[i - 2] * (i - 1) % Mo) % Mo;
tot = tim = 0;
for (int i = 1; i <= m; i ++) {
int u, v;
scanf("%d%d", &u, &v);
link(u, v), link(v, u);
path[i].u = u, path[i].v = v;
}
if (!remake()) return;
for (int i = 1; i <= n; i ++) flag[i] = 0;
int ans = 1;
for (int i = 1; i <= n; i ++) {
if (flag[i]) continue;
dp(i, 0);
ans = 1ll * ans * f[i] % Mo;
}
printf("%d\n", ans);
}
int main() {
int t;
scanf("%d", &t);
for (int i = 1; i <= t; i ++) solve();
}