無標號無根樹計數

2022-10-09 00:21:28 字數 4554 閱讀 5769

這道題題解區有很多神仙題解,講解得很全面,本篇題解僅做一些細節補充和**實現方面的講解。

求 \(n\) 個點的無標號無根樹數量,答案對 \(998,244,353\) 取模。(\(1\le n\le 2\times10^5\))

注意到無根樹這個限制比較噁心,因為對於一棵樹我們總要找個根才好處理各種東西。所以考慮先計算無標號有根樹的數量,然後減去根不是重心的方案。

而對於有根樹,欽定完乙個根之後剩下 \(n-1\) 個點會分成若干個非空集合(且非空集合之間順序無所謂,這也是為什麼後文用的 ogf 而不是 egf),每個集合對應一棵子樹,而每棵子樹又是類似的結構,即具有遞迴的形式。這樣如果我們設答案的 ogf 為:

\[f(x)=\sum_f_nx^n

\]則它應該滿足等式:

\[[x^n]f(x)=[x^]\prod_\left(\sum_x^\right)^

\]其實就是把選擇集合的過程當做揹包,每種集合的大小當成一種物品,會貢獻 \(f_i\) 的方案數,結合上面說的無標號有根樹的性質很容易得出這個等式。根據:

\[\left(\dfrac\right)^=\left(\sum_x^\right)^

\]能把原式的 \(\sum\) 乾掉:

\[[x^n]f(x)=[x^]\prod_\left(\dfrac\right)^

\]這裡再簡單乘上乙個 \(x\) 就能把 \([x^n],[x^]\) 乾掉了:

\[f(x)=x\prod_\left(\dfrac\right)^

\]看來我們問題的關鍵就落在了 \(\prod_\left(\frac\right)^\) 這個東西上面。注意 \(\prod\) 是很難處理的,所以考慮取對數轉化為 \(\sum\):

\[\begin\prod_\left(\dfrac\right)^&=\exp\left(\sum_\ln\left(\dfrac\right)^\right)\\&=\exp\left(\sum_-f_k\ln(1-x^k)\right)\end

\]現在壓力來到了 \(\ln(1-x^k)\) 這邊。因為 \(\ln\) 很難處理,但它的導數很好處理,所以考慮求導:

\[\ln(1-x^k)=\int\dfrac}x}\ln(1-x^k)=\int\dfrac}

\]我們能看到現在積分號裡面的就是乙個我們知道對應冪級數的封閉形式了:

\[\int\dfrac}=-\int\sum_kx^=-\int\sum_kx^

\]現在這個式子就很好看了,只需要把積分號算進去:

\[-\int\sum_kx^=-\sum_\dfrac}=-\sum_\dfrac}

\]好,這東西的另一種形式顯然更便於式子的化簡,我們代回去:

\[\begin\exp\left(\sum_-f_k\ln(1-x^k)\right)&=\left(\exp\sum_f_k\sum_\dfrac}\right)\\&=\exp\left(\sum_\dfrac\sum_f_kx^\right)\\&=\exp\left(\sum_\dfrac\right)\end

\]啊這就好多了,注意到我們有等式:

\[f(x)=x\exp\left(\sum_\dfrac\right)

\]還是類似 \(\ln\),遇到 \(\exp\) 考慮求導:

\[\beginf'(x)&=\left(x\exp\left(\sum_\dfrac\right)\right)'\\&=x'\exp\left(\sum_\dfrac\right)+x\left(\exp\left(\sum_\dfrac\right)\right)'\\&=\dfrac+x\exp\left(\sum_\dfrac\right)\left(\sum_\dfrac\right)'\\&=\dfrac+f(x)\sum_\dfrac}\\&=\dfrac+f(x)\sum_f'(x^n)x^\end

\]兩邊同時乘上 \(x\),會讓式子更好看:

\[xf'(x)=f(x)+f(x)\sum_f'(x^n)x^n

\]注意到這個等式哪哪都好,就是最後乙個求和式很噁心。考慮定義 \(g(x)=\sum_f'(x^n)x^n\),並研究一下 \([x^i]g(x)\) 的取值。讓我們把 \(g(x)\) 展開:

\[\beging(x)&=\sum_x^n\sum_jf_jx^\\&=\sum_\sum_jf_jx^\end

\]這樣的話,對於 \(x^i\) 做貢獻的所有 \(jf_j\) 應該滿足 \(j|i\),即:

\[[x^i]g(x)=\sum_f_jj

\]把 \(g(x)\) 帶回原式:

\[xf'(x)-f(x)=f(x)g(x)

\]而我們發現 \(xf'(x)\) 其實就是:

\[\sum_nf_nx^n

\]所以:

\[[x^n]f(x)=\dfrac

\]即:

\[f_n=\dfrac^(f_kg_)},f_1=1

\]\[f_\times g_\rightarrow f_

\]來算左邊對右邊的貢獻。但這裡因為只求出了 \(l\sim mid\) 的 \(f\),\(g_\) 的值還沒有計算,這題這樣做顯然是錯誤的。而正確的策略是,當 \(l=0\) 時:

\[f_\times g_\rightarrow f_

\]這時候顯然有貢獻漏掉了。考慮當 \(l>0\) 時補回來:

\[\beging_\times f_\\f_\times g_\end\rightarrow f_

\]這樣就能補回來漏掉的貢獻了。

現在求出來 \(f(x)\) 了,不要忘了我們要計數的是無根樹不是有根樹。所以要減掉根不是重心的方案。當 \(n\) 是奇數時,顯然原樹只能有乙個重心。而重心的定義是所有子樹大小均小於等於 \(\lfloor\frac\rfloor\),所以我們只需要欽定一棵子樹大小大於 \(\lfloor\frac\rfloor\) 即可,要減去的方案數為:

\[\sum_\rfloor+1}^f_kf_

\]即看成兩棵有根樹分別處理,然後把大小為 \(k\) 的那個接到另乙個的根上。而當 \(n\) 為偶數時,麻煩了,因為可能出現兩個重心的情況,且其中乙個還是根,即存在一棵子樹大小恰等於 \(\frac\)。而現在還是有兩種情況,第一種是這棵子樹和去掉這棵子樹後的原樹(模擬奇數情況下 \(f_k\) 和 \(f_\))恰好同構,那這個就不會重複計數了,因為這倆根本質上是一樣的。而其他情況就會重複計數一次,減掉的方案數即在所有可能的形態中選倆接起來:

\[\dbinom}}

\]這樣這道題就解決了,時間複雜度 \(\mathcal(n\log^2n)\)。在剛剛的推導中我把 \(f_0\) 這一項都忽略了,但在實現裡面我是算上了的,但顯然影響不大。

#include #include const int n = 1e6 + 10, mod = 998244353; typedef long long ll;

int f[n], g[n], a[n], b[n], rev[n], lim, n, m;

inline int ksm(int a, int b)

return ret;

}inline void init(int n)

inline void ntt(int* f, int len, int on)

}if (on == mod - 2) for (int i = 0, inv = ksm(len, on); i < len; ++i) f[i] = (ll)f[i] * inv % mod;

}inline void mul(int* f, int lf, int* g, int lg)

inline void cdq(int l, int r)

int mid = (l + r) >> 1; cdq(l, mid); init(r - l);

if (l > 0)

mul(f + l, mid - l, g, !l ? mid : r - l);

for (int i = mid; i < r; ++i) (f[i] += a[i - l]) %= mod;

cdq(mid, r);

}signed main()

題做完了,我們來看看從這道題我們能得到什麼。注意到剛剛我們處理的式子:

\[\prod_\left(\frac\right)^

\]的組合意義是把原集合分成若干個非空子集,且順序無所謂,大小為 \(i\) 的組方案數為 \(f_i\),對應的總方案數。這個東西跟 egf 中的 \(\exp\) 似乎很相似,所以我們考慮給它單獨起乙個名字:euler 變換,\(f(x)\) 的 euler 變換通常記為 \(\mathcalf(x)\)。剛剛已經得出了它的兩種形式:

\[\mathcalf(x)=\prod_\left(\frac\right)^=\exp\left(\sum_\dfrac\right)

\]個人認為算是 ogf 版的 「\(\exp\)」,不考慮分成子集的前後順序。

pps. 本題還可以用牛頓迭代在 \(\mathcal(n\log n)\) 的時間內求解,但那個方法涉及到 \(\exp\),所以常數會比較**,具體可以看其他神仙的題解。

ppps. 關於最後扔出的 euler 變換第二個形式的證明,題解區還給出了用群論方法證明的思路。

pppps. 我的**常數大得要命,看看理解咋實現可以,借鑑具體實現方式就算了。

無標號生成樹計數

做模擬賽的時候碰到了,感覺稍微有點意思,寫來自己看。無標號有根樹 設f n 表示樹的大小為 n 的方案數,其生成函式f z n 0f nzn。考慮生成函式的組合意義,fn 1可以由若干個無序的不同大小的 若干個無序的相同大小的本質不同的子樹 拼成,對於大小為 k 的樹,作為多棵子樹時他可以貢獻的不同...

無根樹同構 hash

先貼上位址 判斷有根樹同構 1.直接用括號最小表示法 2.利用括號最小表示法的思想進行hash 判斷無根樹同構 1.找到樹的重心.2.以重心為根,把無根樹轉化成有根樹.按照有根樹同構的方法判斷是否同構.同構的過程中,為什麼可以sort.我們知道,對於樹來說,樹的節點繞著它的父節點旋轉,樹的結構就不會...

無根樹轉有根樹

乙個n n 1000000 個結點的無根樹的各條邊,並指定乙個根結點,要求把樹轉化為有根樹。輸入 結點的數目n,無根樹的各條邊,輸入乙個根結點號。輸出 各個結點的父親編號。執行結果 演算法實現 為方便起見,我們用了stl中的vector來儲存邊,g u 表示u結點的相鄰結點的編號。樹的儲存結構定義 ...