給定一棵有 \(n\) 個節點的樹,知足 \(n\) 爲偶數。初始時,每條邊都爲白色。ios
如今請你將這些點兩兩配對成 \(\frac{n}{2}\) 個無序點對。每一個點對之間的的路徑都會被染成黑色git
求有多少種配對方案,使得樹上沒有白邊?spa
\(n\le 5000\)code
樹上的路徑很難直接考慮。ip
有一種容斥的作法:記邊集爲 E ,枚舉 T 子集中的邊強制爲白邊,其他的不做限制, 那麼:ci
\[ Ans = \sum_{T\subseteq E} (-1) ^ {T} F(T) \]get
\(F(T)\) 爲強制 T 的邊爲白邊的方案數。input
把 T 刪掉後不難發現樹變成了若干個聯通塊,顯然這若干個連通塊是獨立的。string
對於一個大小爲 n 的連通塊,兩點隨便配對的方案數是 \((n - 1) * (n - 3) * \cdots * 1\),記爲 \(g(n)\) 。it
然而暴力枚舉 T 複雜度太高,考慮樹型 dp ,須要知道的狀態是 u 當前所在聯通塊大小以及容斥係數(即 T 的奇偶)。
設 \(dp[u][i][0/1]\) 爲 u 子樹內,u 所在聯通塊大小爲 i ,T 的奇偶性是 0 / 1 的方案數。
轉移就合併 u 的子樹 v ,同時考慮 <u, v> 這條邊是否選入 T 集合,有點作 01 揹包的感受。
\[ dp[v][i][a]\times dp[u][i][b] \rightarrow dp'[u][i + j][a\oplus b]\\ dp[v][j][a]\times dp[u][i][b]\times g[i] \rightarrow dp'[u][i][a\oplus b\oplus 1] \]
最後答案就是 \(|T|\) 爲偶數的 - \(|T|\) 爲奇數的。
\[ Ans = \sum_{i = 1} ^ n (dp[root][i][0] - dp[root][i][1]) \times g[i] \]
#include <cstdio> #include <cstring> #include <vector> #include <iostream> #include <algorithm> using namespace std; #define End exit(0) #define LL long long #define mp make_pair #define SZ(x) ((int) x.size()) #define GO cerr << "GO" << endl #define DE(x) cout << #x << " = " << x << endl #define DEBUG(...) fprintf(stderr, __VA_ARGS__) void proc_status() { freopen("/proc/self/status","r",stdin); string s; while(getline(cin, s)) if (s[2] == 'P') { cerr << s << endl; return; } } template<typename T> inline T read() { register T x = 0; register char c; register int f(1); while (!isdigit(c = getchar())) if (c == '-') f = -1; while (x = (x << 1) + (x << 3) + (c ^ 48), isdigit(c = getchar())); return x * f; } template<typename T> inline bool chkmin(T &a,T b) { return a > b ? a = b, 1 : 0; } template<typename T> inline bool chkmax(T &a,T b) { return a < b ? a = b, 1 : 0; } const int maxN = 5000 + 2; const int mod = 1e9 + 7; vector<int> adj[maxN + 2]; int g[maxN + 2], n, size[maxN + 2]; int dp[maxN + 2][maxN + 2][2]; void input() { n = read<int>(); for (int i = 1; i < n; ++i) { int u = read<int>(), v = read<int>(); adj[u].push_back(v), adj[v].push_back(u); } } void dfs(int u, int f) { static int tmp[maxN + 2][2]; size[u] = 1; dp[u][1][0] = 1; for (int v : adj[u]) if (v != f) { dfs(v, u); for (int i = 0; i <= size[u]; ++i) for (int j = 0; j <= size[v]; ++j) for (int a = 0; a < 2; ++a) for (int b = 0; b < 2; ++b) { (tmp[i + j][a ^ b] += (LL) dp[v][j][a] * dp[u][i][b] % mod) %= mod; if (!(j & 1)) (tmp[i][a ^ b ^ 1] += (LL) dp[v][j][a] * dp[u][i][b] % mod * g[j] % mod) %= mod; } size[u] += size[v]; for (int i = 0; i <= size[u]; ++i) for (int j = 0; j < 2; ++j) dp[u][i][j] = tmp[i][j], tmp[i][j] = 0; } } void solve() { g[0] = 1; for (int i = 2; i <= n; i += 2) g[i] = (LL) g[i - 2] * (i - 1) % mod; dfs(1, 0); int ans = 0; for (int i = 1; i <= n; ++i) (ans += ((LL) dp[1][i][0] - dp[1][i][1] + mod) * g[i] % mod) %= mod; cout << ans << endl; } int main() { #ifndef ONLINE_JUDGE freopen("xhc2.in", "r", stdin); freopen("xhc2.out", "w", stdout); #endif input(); solve(); return 0; }
仍是基於上面的容斥。
設 \(dp[u][i]\) 爲 u 子樹內還有 i 個點沒有匹配,但考慮了容斥係數的答案。
合併子樹後注意下 \(dp[u][0]\) 的轉移要乘以 -1 的容斥係數(根除外)
#include <cstdio> #include <cstring> #include <vector> #include <iostream> #include <algorithm> using namespace std; #define End exit(0) #define LL long long #define mp make_pair #define SZ(x) ((int) x.size()) #define GO cerr << "GO" << endl #define DE(x) cout << #x << " = " << x << endl #define DEBUG(...) fprintf(stderr, __VA_ARGS__) void proc_status() { freopen("/proc/self/status","r",stdin); string s; while(getline(cin, s)) if (s[2] == 'P') { cerr << s << endl; return; } } template<typename T> inline T read() { register T x = 0; register char c; register int f(1); while (!isdigit(c = getchar())) if (c == '-') f = -1; while (x = (x << 1) + (x << 3) + (c ^ 48), isdigit(c = getchar())); return x * f; } template<typename T> inline bool chkmin(T &a,T b) { return a > b ? a = b, 1 : 0; } template<typename T> inline bool chkmax(T &a,T b) { return a < b ? a = b, 1 : 0; } const int maxN = 5000 + 2; const int mod = 1e9 + 7; int n; int ver[maxN << 1], nxt[maxN << 1], head[maxN + 2]; int dp[maxN + 2][maxN + 2], tmp[maxN + 2], size[maxN + 2], g[maxN + 2]; inline void Inc(int &x) { x < 0 ? x += mod : 0; } inline void Dec(int &x) { x >= mod ? x -= mod : 0; } void link(int u, int v) { static int ecnt = 0; ver[++ecnt] = v, nxt[ecnt] = head[u], head[u] = ecnt; } void dfs(int u, int fa) { dp[u][1] = 1; size[u] = 1; for (int i = head[u]; i; i = nxt[i]) { int v = ver[i]; if (v == fa) continue; dfs(v, u); for (int i = 0; i <= size[u]; ++i) for (int j = 0; j <= size[v]; ++j) Dec(tmp[i + j] += 1ll * dp[u][i] * dp[v][j] % mod); size[u] += size[v]; for (int i = 0; i <= size[u]; ++i) dp[u][i] = tmp[i], tmp[i] = 0; } for (int i = 1; i <= size[u]; ++i) Inc(dp[u][0] -= 1ll * dp[u][i] * g[i] % mod); } int main() { #ifndef ONLINE_JUDGE freopen("xhc.in", "r", stdin); freopen("xhc.out", "w", stdout); #endif n = read<int>(); for (int i = 1; i < n; ++i) { int u = read<int>(), v = read<int>(); link(u, v), link(v, u); } g[0] = 1; for (int i = 2; i <= n; ++i) g[i] = 1ll * g[i - 2] * (i - 1) % mod; dfs(1, 0); printf("%d\n", (mod - dp[1][0]) % mod); return 0; }