【ZJOI2016】小星星 容斥+樹形DP
阿新 • • 發佈:2021-06-24
題意
UOJ #185.
給出一個\(n(n \le 17)\) 的樹和 \(n\) 個點的圖,求有多少種樹點和圖點的對應方案是“好的”
“好的”方案,如果現在樹上兩點間存在邊,那麼要求圖上對應的這兩點之間也有邊。
題解
考慮用 \(dp\) 去計算方案數,如果需要確定一個順序來dp,顯然是從葉子向上樹形dp。
不妨設 1 為整棵樹的根,現在用 \(f_{i, j}\) 表示樹上一顆以 \(i\) 為根的子樹全部確定對應點,\(i\) 樹點對應 \(j\) 圖點。
但這樣計算有一個問題,會有多個樹點選擇了同一個圖點,會算重。
這時注意到 \(n\)
狀態為\(1\) 的圖點可以選擇,其他不能。然後每個狀態計算一下,最後統計就好。
總的來說就是先容斥再每個狀態都樹形DP。
程式碼
#include<bits/stdc++.h> using namespace std; #define pb push_back #define mp make_pair typedef long long ll; const int N = 18; int n, m, cnt, id[N]; int e, to[N << 1], nxt[N << 1], hd[N]; ll f[N][N]; bool ed[N][N]; void add(int u, int v){ to[++e] = v; nxt[e] = hd[u]; hd[u] = e; } void dfs(int u, int fa){ for(int i = hd[u]; i; i = nxt[i]){ int v = to[i]; if(v == fa) continue; dfs(v, u); } for(int i = 1; i <= cnt; i++){ f[u][i] = 1; for(int j = hd[u]; j; j = nxt[j]){ int v = to[j]; if(v == fa) continue; ll sum = 0; for(int k = 1; k <= cnt; k++) if(ed[id[i]][id[k]]) sum += f[v][k]; f[u][i] *= sum; } }//樹形DP return; } int main(){ scanf("%d%d", &n, &m); for(int i = 1, u, v; i <= m; i++) scanf("%d%d", &u, &v), ed[u][v] = ed[v][u] = 1; for(int i = 1, u, v; i < n; i++) scanf("%d%d", &u, &v), add(u, v), add(v, u); ll ans = 0; for(int i = 0; i < (1 << n); i++){ cnt = 0; for(int j = 1; j <= n; j++) if(i & (1 << (j - 1))) id[++cnt] = j; dfs(1, 0); ll sum = 0; for(int j = 1; j <= cnt; j++) sum += f[1][j]; ans += (((cnt ^ n) & 1) ? -1 : 1) * sum; }//容斥 printf("%lld\n", ans); return 0; }