Address
洛谷P3687
BZOJ4784
UOJ#290
LOJ#2250
Solution
首先,如果原图不是仙人掌,就直接输出
。
否则对图进行一遍 DFS ,找出所有的环并去掉,原图变成森林。答案显然是每棵树的答案的乘积。
考虑到在如果在树上的点对
之间连边,就相当于把
到
的路径上的所有边标记为环边。
但存在两个问题:
(1)不能有重边,也就是说如果树上已经有边
就不能连边
。
(2)每一条边不一定要全部被标记为环边。
但撕烤后发现上面这两个条件可以抵消。
也就是说,如果我们假设可以有重边,那么问题再次转化成选出一些路径集合
覆盖树上所有的边。
我们可以开始愉快地树形 DP 了!
表示覆盖
的子树内所有边的方案数。
表示覆盖
的所有子树内所有边,并选出一条端点为
的路径向
的父亲延伸的方案数。
表示将
个元素分到一些集合内,使得每个集合非空且大小不超过
的方案数。
表示将
个元素分到一些集合内,使得每个集合非空且大小不超过
,所有方案中大小为
的集合个数之和。
边界:
的转移:
的转移:
的转移也就是枚举
的子节点
,讨论两种情况(一为路径
单独存在,二为路径
不单独存在),然后需要把子节点分组,每个组大小不超过
,对于大小为
的组,在点
把来自两条处于不同子树的路径连接成一条:
也差不多:
答案为所有森林的根的
之积。
Code
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Edge(u) for (int e = adj[u], v = go[e]; e; e = nxt[e], v = go[e])
#define Tree(u) for (int e = adj[u], v; e; e = nxt[e]) if ((v = go[e]) != fu)
using namespace std;
inline int read()
{
int res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
return bo ? ~res + 1 : res;
}
const int N = 5e5 + 5, M = N << 2, ZZQ = 998244353;
int n, m, ecnt, nxt[M], adj[N], go[M], f[N], g[N], h[N], w[N], cnt[N],
dfn[N], T, d[N];
bool is, cut[M], vis[N];
void add_edge(int u, int v)
{
nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v;
nxt[++ecnt] = adj[v]; adj[v] = ecnt; go[ecnt] = u;
}
void dfs(int u, int fe)
{
dfn[u] = ++T;
cnt[u] = 0;
Edge(u)
{
if (e == (fe ^ 1)) continue;
if (!dfn[v]) dfs(v, e), cnt[u] += cnt[v];
else if (dfn[u] < dfn[v]) cnt[u]--;
else cnt[u]++;
}
if (u > 1 && !cnt[u]) cut[fe] = cut[fe ^ 1] = 1;
if (cnt[u] >= 2) is = 0;
}
int dp(int u, int fu)
{
d[u] = 0;
f[u] = 1; vis[u] = 1;
Tree(u)
{
if (!cut[e]) continue;
d[u]++;
dp(v, u);
f[u] = 1ll * f[u] * (f[v] + g[v]) % ZZQ;
}
g[u] = 1ll * f[u] * h[d[u]] % ZZQ;
f[u] = 1ll * f[u] * w[d[u]] % ZZQ;
return f[u];
}
void work()
{
int i, x, y, ans = 1;
n = read(); m = read();
ecnt = 1; T = 0;
For (i, 1, n) adj[i] = dfn[i] = 0, vis[i] = 0;
while (m--) x = read(), y = read(), add_edge(x, y);
For (i, 2, ecnt) cut[i] = 0;
is = 1;
dfs(1, 0);
if (!is) return (void) puts("0");
For (i, 1, n) if (!vis[i])
ans = 1ll * ans * dp(i, 0) % ZZQ;
printf("%d\n", ans);
}
int main()
{
int i, T = read();
w[0] = 1;
For (i, 1, 500000)
{
w[i] = w[i - 1];
if (i > 1) w[i] = (w[i] + 1ll * (i - 1) * w[i - 2] % ZZQ) % ZZQ;
h[i] = (h[i - 1] + w[i - 1]) % ZZQ;
if (i > 1) h[i] = (h[i] + 1ll * (i - 1) * h[i - 2] % ZZQ) % ZZQ;
}
while (T--) work();
return 0;
}