1. 程式人生 > >【題解】ZJOI2017仙人掌

【題解】ZJOI2017仙人掌

turn 知識 namespace dfs lse time 節點 pan ast

  感覺這題很厲害啊,雖然想了一天多但還是失敗了……(;д;)

  這題首先註意到給定圖中如果存在環其實對於答案是沒有影響的。然後關鍵之處就在於兩個 \(dp\) 數組,其中 \(f[u]\) 表示以 \(u\) 為根的子樹中能構成仙人掌的方案數, 而 \( g[x] \) 則表示 \(x\) 個節點之間兩兩相互搭配(可以不搭配)的總方案數。轉移則為:

 \(f[u] = \prod f[v] * g[tot + [u != root]]\)

  其中 \(v\) 為 \(u\) 的兒子節點,而 \(tot\) 表示 \(u\) 的總兒子個數。為什麽這樣做是對的呢?我也感到非常的困惑。之前自己在思考的時候其實有一個問題一直難住我:一個節點的兒子之間可以相互連邊,這怎樣處理?但此時我們將這些方案巧妙地連接在了一起。我們可以默認為求出來的 \(f[u]\) 中的方案數均為有一條邊連向外界的方案。當這個方案匹配到另一子樹的一種方案上的時候,表示這兩條連向外界的邊連接在了一起。若有沒有匹配的,說明這條邊沒有連出去或連向根節點(若連向根節點且該點為兒子節點則說明沒有連出去),但一樣是合法的。

  非常的厲害啊~其實感覺自己現在各種知識儲備都還算可以了,但就是不夠大膽,不能勇敢的提出一些想法和設想。一定要努力放開自己的思維,先猜測,再證明~

#include <bits/stdc++.h>
using namespace std;
#define maxn 1000000
#define mod 998244353
#define int long long
int n, m, dep[maxn], g[maxn];
int timer, dfn[maxn], f[maxn];
int fa[maxn], mark[maxn], tot;
int cnt, ans;

struct edge { int cnp, head[maxn], to[maxn], last[maxn]; edge() { cnp = 1; } void add(int u, int v) { to[cnp] = v, last[cnp] = head[u], head[u] = cnp ++; to[cnp] = u, last[cnp] = head[v], head[v] = cnp ++; } }E1; struct node { int id, dep; }a[maxn];
bool cmp(node a, node b) { return a.dep < b.dep; } int read() { int x = 0, k = 1; char c; c = getchar(); while(c < 0 || c > 9) { if(c == -) k = -1; c = getchar(); } while(c >= 0 && c <= 9) x = x * 10 + c - 0, c = getchar(); return x * k; } void pre() { g[0] = g[1] = 1; for(int i = 2; i < maxn; ++ i) g[i] = (g[i - 1] + (i - 1)*g[i - 2]) % mod; return; } void Tarjan(int u) { dfn[u] = ++ timer; for(int i = E1.head[u]; i; i = E1.last[i]) { int v = E1.to[i]; if(dfn[v]) continue; fa[v] = u; dep[v] = dep[u] + 1; Tarjan(v); } return; } void dfs(int u, int rt) { mark[u] = -1; f[u] = 1; int tot = 0; for(int i = E1.head[u]; i; i = E1.last[i]) { int v = E1.to[i]; if(v == fa[u] || mark[v] != 1) continue; tot ++; dfs(v, 0); f[u] = f[u] * f[v] % mod; } if(!rt) f[u] = f[u] * g[tot + 1] % mod; else f[u] = f[u] * g[tot] % mod; return; } void Work() { n = read(), m = read(); E1.cnp = 2; for(int i = 1; i <= n; i ++) mark[i] = fa[i] = dep[i] = dfn[i] = E1.head[i] = 0; for(int i = 1; i <= m; i ++) { int u = read(), v = read(); E1.add(u, v); } dep[1] = 1; Tarjan(1); for(int i = 1; i <= m; i ++) { int u = E1.to[i << 1], v = E1.to[i << 1 | 1]; if(dfn[u] < dfn[v]) swap(u, v); while(u != v) { if(mark[u] == 2) { printf("0\n"); return; } mark[u] ++; u = fa[u]; } } for(int i = 1; i <= n; i ++) a[i].id = i, a[i].dep = dep[i]; sort(a + 1, a + n + 1, cmp); ans = 1; for(int i = 1; i <= n; i ++) { int x = a[i].id; if(mark[x] == -1) continue; dfs(x, 1); ans = ans * f[x] % mod; } printf("%lld\n", ans); return; } signed main() { pre(); int T = read(); while(T --) Work(); return 0; }

  

【題解】ZJOI2017仙人掌