Address
洛谷P3687
BZOJ4784
UOJ#290
LOJ#2250
Solution
首先,如果原图不是仙人掌,就直接输出
0
0
0 。
否则对图进行一遍 DFS ,找出所有的环并去掉,原图变成森林。答案显然是每棵树的答案的乘积。
考虑到在如果在树上的点对
(
u
,
v
)
(u,v)
(u,v) 之间连边,就相当于把
u
u
u 到
v
v
v 的路径上的所有边标记为环边。
但存在两个问题:
(1)不能有重边,也就是说如果树上已经有边
(
u
,
v
)
(u,v)
(u,v) 就不能连边
(
u
,
v
)
(u,v)
(u,v) 。
(2)每一条边不一定要全部被标记为环边。
但撕烤后发现上面这两个条件可以抵消。
也就是说,如果我们假设可以有重边,那么问题再次转化成选出一些路径集合
{
(
u
,
v
)
∣
u
≠
v
}
\{(u,v)|u\ne v\}
{(u,v)∣u̸=v} 覆盖树上所有的边。
我们可以开始愉快地树形 DP 了!
f
[
u
]
f[u]
f[u] 表示覆盖
u
u
u 的子树内所有边的方案数。
g
[
u
]
g[u]
g[u] 表示覆盖
u
u
u 的所有子树内所有边,并选出一条端点为
u
u
u 的路径向
u
u
u 的父亲延伸的方案数。
w
[
i
]
w[i]
w[i] 表示将
i
i
i 个元素分到一些集合内,使得每个集合非空且大小不超过
2
2
2 的方案数。
h
[
i
]
h[i]
h[i] 表示将
i
i
i 个元素分到一些集合内,使得每个集合非空且大小不超过
2
2
2 ,所有方案中大小为
1
1
1 的集合个数之和。
边界:
w
[
0
]
=
1
,
h
[
0
]
=
0
,
f
[
叶
子
节
点
]
=
0
,
g
[
叶
子
节
点
]
=
0
w[0]=1,h[0]=0,f[叶子节点]=0,g[叶子节点]=0
w[0]=1,h[0]=0,f[叶子节点]=0,g[叶子节点]=0
w
w
w 的转移:
w
[
i
]
=
w
[
i
−
1
]
+
w
[
i
−
2
]
×
(
i
−
1
)
w[i]=w[i-1]+w[i-2]\times(i-1)
w[i]=w[i−1]+w[i−2]×(i−1)
h
h
h 的转移:
h
[
i
]
=
h
[
i
−
1
]
+
w
[
i
−
1
]
+
h
[
i
−
2
]
×
(
i
−
1
)
h[i]=h[i-1]+w[i-1]+h[i-2]\times(i-1)
h[i]=h[i−1]+w[i−1]+h[i−2]×(i−1)
f
f
f 的转移也就是枚举
u
u
u 的子节点
v
v
v ,讨论两种情况(一为路径
(
u
,
v
)
(u,v)
(u,v) 单独存在,二为路径
(
u
,
v
)
(u,v)
(u,v) 不单独存在),然后需要把子节点分组,每个组大小不超过
2
2
2 ,对于大小为
2
2
2 的组,在点
u
u
u 把来自两条处于不同子树的路径连接成一条:
f
[
u
]
=
w
[
d
[
u
]
]
∏
v
∈
s
o
n
[
u
]
(
f
[
v
]
+
g
[
v
]
)
f[u]=w[d[u]]\prod_{v\in son[u]}(f[v]+g[v])
f[u]=w[d[u]]v∈son[u]∏(f[v]+g[v])
g
g
g 也差不多:
g
[
u
]
=
h
[
d
[
u
]
]
∏
v
∈
s
o
n
[
u
]
(
f
[
v
]
+
g
[
v
]
)
g[u]=h[d[u]]\prod_{v\in son[u]}(f[v]+g[v])
g[u]=h[d[u]]v∈son[u]∏(f[v]+g[v])
答案为所有森林的根的
f
f
f 之积。
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;
}