[BZOJ4784][Zjoi2017]仙人掌(樹形DP)
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)