一道漲姿式的EGF好題,官方題解我並無徹底看懂,嘗試用指數型生成函數和組合意義的角度推了一波。考場上只得了 44 分也暴露了我在數數的一些基本套路上的不足,後面的 \(\exp\) 是真的神仙,作不出來固然很正常,並且我當時也不怎麼會多項式。函數
考慮公共邊組成 \(k\) 個聯通塊,答案就是 \(y^k\) ,並查集維護一下便可,複雜度 \(\mathcal O(n\log n)\) 。spa
namespace task0{ map<pair<int, int>, int> mp; int fa[N]; inline int ask(int x){ return x == fa[x] ? x : fa[x] = ask(fa[x]); } inline void solve(){ for(int i = 1; i <= n; i++) fa[i] = i; for(int i = 1, x, y; i <= 2 * (n - 1); i++) read(x), read(y), mp[make_pair(min(x, y), max(x, y))]++;; for(map<pair<int, int>, int>::iterator it = mp.begin(); it != mp.end(); it++) if(it->second == 2) if(ask(it->first.first) != ask(it->first.second)) fa[ask(it->first.first)] = ask(it->first.second); int tot = 0; for(int i = 1; i <= n; i++) if(fa[i] == i) tot++; cout << Pow(Y, tot) << endl; } }
考慮兩棵樹每有一條公共邊,聯通塊個數就 \(-1\) ,不妨設一開始答案爲 \(y^n\) ,每有一條公共邊,其對答案的貢獻就是 \(z=y^{-1}\) 。code
先按照官方題解說的,引入組合恆等式
\[ z^k=(z-1+1)^k=\sum_{i=1}^k {k\choose i }(z-1)^i \]
考慮紅樹和藍樹的最終形態若是剛好有 \(k\) 條公共邊,那麼對答案的貢獻就是 \(z^k\) ,考慮枚舉這種形態的全部公共邊的每個子集,每個大小爲 \(i\) 的子集貢獻爲 \((z-1)^i\) ,就能夠獲得這個式子的組合意義。get
不妨枚舉一個大小爲 \(i\) 的公共邊集 \(S\) ,(必定是藍樹的一個邊集),而後考慮全部公共邊集是 \(S\) 的超集的方案,其對答案的貢獻就是 \((z-1)^i\) 乘上覆蓋它的紅樹的數量。it
假設當前有 \(i\) 條公共邊,造成了 \(m=n-i\) 個聯通塊,其中第 \(i\) 個聯通塊大小爲 \(a_i\) ,根據 \(prufer\) 經典結論 ,能夠獲得覆蓋這個它的紅樹的數量。
\[ n^{m-2}\prod a_i \]
那麼全部狀況對答案的貢獻和就是
\[ n^{-2}\sum_{m=1}^n(z-1)^{n-m}n^m\prod_{\sum_{i=1}^m a_i=n,a_i\geq1} a_i \]
考慮後面式子的組合意義是在每一個聯通塊中剛好選出一個點的方案數,因此能夠令 \(dp[u][0/1]\) 表示藍樹以 \(u\) 爲根的聯通塊是否選出一個點的總貢獻,此時每一個聯通塊有 \(n\) 的貢獻,每選一條公共邊有 \(z\) 的貢獻,討論 \(u\) 的每一個兒子是否和 \(u\) 在一個聯通塊便可,複雜度 \(O(n)\) 。class
namespace task1{ int dp[N][2], Z; vector<int> g[N]; inline void dfs(int u, int fa){ dp[u][0] = 1, dp[u][1] = n; for(int i = 0; i < (int) g[u].size(); i++){ int v = g[u][i]; if(v == fa) continue; dfs(v, u); dp[u][1] = (1ll * dp[v][1] * dp[u][1] % P + 1ll * (Z - 1) * (1ll * dp[v][1] * dp[u][0] % P + 1ll * dp[v][0] * dp[u][1] % P) % P) % P; dp[u][0] = (1ll * dp[v][1] * dp[u][0] % P + 1ll * dp[v][0] * dp[u][0] % P * (Z - 1) % P) % P; } } inline void solve(){ Z = Pow(Y); for(int i = 1, x, y; i < n; i++){ read(x), read(y); g[x].push_back(y), g[y].push_back(x); } dfs(1, 0); cout << 1ll * dp[1][1] * Pow(Y, n) % P * Pow(n, P - 3) % P << endl; } }
仍是利用以前的組合恆等式,枚舉一個公共邊集 \(S\) ,算出其對答案的貢獻。map
這一步等價於將 \(n\) 拆分紅至多 \(m=n-|S|\) 個聯通塊,每一個聯通塊內部已經固定,計算全部拆分方式對答案的貢獻:
\[ \sum_{m=1}^n(z-1)^{n-m}\sum_{\sum_{i=1}^{m}a_i=n,a_i\geq1}\dfrac{n!}{m!\prod a_i!} \prod a_i^{a_i-2}\left(n^{m-2}\prod a_i\right)^2 \\ =(z-1)^nn^{-4}\sum_{m=1}^n\sum_{\sum_{i=1}^ma_i=n,a_i\geq1}\dfrac{n!}{m!\prod a_i!}\prod(z-1)^{-1}n^2a_i^{a_i} \\ \]集合
也就是說每個大小爲 \(a_i\) 的聯通塊對答案的貢獻爲 \((z-1)^{-1}n^2a_i^{a_i}\) ,對這些聯通塊作有標號的集合拼接再乘上以前的係數能夠獲得答案,前面的 \(\dfrac{n!}{m!\prod a_i!}\) 的組合意義是對於當前枚舉的拼接方式,去除集合內部順序以及拼接順序的影響後的方案數。di
其實到這一步 EGF 的形式就已經很顯然了,考慮列出每一個聯通塊的指數型生成函數。
\[ f(x) = \sum_{i=1}^{\infty}\dfrac{(z-1)^{-1}n^2i^{i}}{i!}x^i \]
把這個生成函數 \(\exp\) 一下就天然作完了有標號的集合拼接,前面集合拼接的方案數的係數也不用考慮了。因爲 \(\exp\) 後的第 \(n\) 項還有一個 \(\dfrac{x^n}{n!}\) 的形式冪級數要去掉,因此最終式子就變成:
\[ (z-1)^nn^{-4}n![x^n]\exp\left(\sum_{i=1}^{\infty}\dfrac{(z-1)^{-1}n^2i^{i}}{i!}x^i\right) \]
作一遍多項式 \(\exp\) ,注意 \(z = 1\) 也就是 \(y=1\)的時候 \((z-1)^{-1}\) 不存在,須要特判,總複雜度 \(\mathcal O(n \log n)\) 。make
namespace task2{ int js[N], inv[N], ans[N], f[N]; inline void solve(){ if(Y == 1) return (void) (cout << 1ll * Pow(n, n - 2) * Pow(n, n - 2) % P << endl); poly::init(); js[0] = inv[0] = 1; for(int i = 1; i <= n; i++) js[i] = 1ll * js[i-1] * i % P, inv[i] = Pow(js[i], P - 2); int Z = Pow(Y, P - 2), c = 1ll * Pow(Z - 1, n) * Pow(n, P - 5) % P * js[n] % P; int c2 = 1ll * n * n % P * Pow(Z - 1, P - 2) % P; for(int i = 1; i <= n; i++) f[i] = 1ll * c2 * Pow(i, i) % P * inv[i] % P; poly::getexp(f, ans, n + 1); cout << 1ll * ans[n] * c % P * Pow(Y, n) % P; } }