[ZJOI2016]小星星
阿新 • • 發佈:2021-08-01
題面
題解
就是對於節點數相同的一棵樹和一張圖,對於這顆樹的點集的子集向這幅圖的點集的子集建立一個對映,且滿足在樹上的每一條邊的兩個端點對映到圖中時也有邊連線,問這樣的對映方式有多少種。
我們設 \(f[x][j]\) 表示以 \(x\) 為根的子樹,當 \(x\) 對映成 \(j\) 時的方案數。
所以我們可以先直接列舉點集的子集。
然後在 dfs 這棵樹的時候,對於邊 \((x,y)\) ,即某種對映 $x -> i,y -> j $ 我們直接判斷在圖中是存邊 \((i,j)\) 即可。
對於子節點 \(y\) 的所有對映的方案之間顯然是累加關係。
而子節點與父節點的方案之間顯然是乘法關係。
所以轉移關係便很清晰了。
總的時間複雜度是 \(O(n ^ 3 2 ^ n)\)
以下程式碼需要吸氧,但是吸了之後快了 \(4\) 秒就很離譜。
程式碼
#include<cstdio> #include<vector> using namespace std; #define re register typedef long long LL; int n, m, q[25], tot; bool g[20][20]; vector < int > to[20]; LL ans = 0, f[20][20]; inline void add(int u, int v) { to[u].push_back(v); to[v].push_back(u); } void dfs(re int x, re int fa) { for(re unsigned int i = 0; i < to[x].size(); i++) if(to[x][i] != fa) dfs(to[x][i], x); for(re int i = 1; i <= tot; i++) { f[x][q[i]] = 1; for(re unsigned int p = 0; p < to[x].size(); p++) { if(to[x][p] == fa) continue; re LL sum = 0; for(re int j = 1; j <= tot; j++) if(g[q[i]][q[j]]) sum += f[to[x][p]][q[j]]; f[x][q[i]] *= sum; } } } inline void solve() { re int lim = 1 << n; for(re int i = 0; i < lim; i++) { tot = 0; for(re int j = i, k = 1; j; j >>= 1, k++) if(j & 1) q[++tot] = k; dfs(1, 0); for(re int j = 1; j <= tot; j++) ans += (((n - tot) & 1) ? -1 : 1) * f[1][q[j]]; } } int main() { scanf("%d%d", &n, &m); for(re int i = 1, u, v; i <= m; i++) scanf("%d%d", &u, &v), g[u][v] = g[v][u] = 1; for(re int i = 1, u, v; i < n; i++) scanf("%d%d", &u, &v), add(u, v); solve(); printf("%lld\n", ans); return 0; }