1. 程式人生 > >ZJOI2017 仙人掌 轉化模型後的簡單樹形dp

ZJOI2017 仙人掌 轉化模型後的簡單樹形dp

題目大意

給定一個n個點,m條變的無向無自環的連通圖,問都多少種加邊方案使得加完邊的圖是一幅沒有重邊仙人掌。(即滿足任意一條邊只屬於一個簡單環中的無向無自環圖的連通圖)
多組資料。

n5105
m106

解題思路

首先討論給定的圖是樹的情況。

(轉化一)
要求不能有邊存在與兩個簡單環內相當於我們要加入新的邊去覆蓋這個樹,是的每條邊最多被覆蓋一次。

由於每條邊不一定被覆蓋,所以比較麻煩。注意到題目的一個性質,不能存在重邊,那麼意味著我們可以將原來構出仙人掌中不存在環內的邊視為向原來樹中的父親連了一條邊,這個與原問題是等價的。這樣就有了每條樹邊都要被覆蓋一次的限制。

(轉化二)
問題就轉化成了有多少種覆蓋方案使得樹上每條邊恰好被覆蓋一次。

這就很好做了,設
f[i]表示做完以i為根的子樹,且沒有路徑可以向上擴充套件。
g[i]表示做完以i為根的子樹,且有路徑可以向上擴充套件。
h[i]表示有i個點,變成若干個兩個點和一個點組合的方案數。

轉移就很顯然了。
h[0]=h[1]=1
h[i]=h[i1]+h[i2](i1)(i>2)
f[i]=(json[i]g[j])h[|son[i]|]
g[i]=f[i]+(json[i]g[j])h[|son[i]|1]|son[i]|
(分i節點是否為向上覆蓋邊的端點討論)

對於原圖不是樹的情況可以先把原圖不是仙人掌的情況判掉,答案肯定是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(); }