【題解】 SRM686 CyclesNumber 置換+斯特林數 TOC14199
阿新 • • 發佈:2020-10-12
Legend
Link \(\textrm{to TopCoder}\)。
求所有長度為 \(n\) 的置換的迴圈個數的 \(m\) 次方之和。
\(n \le 10^5,m \le 500\)。
Editorial
設 \(x_i\) 表示 \(i\) 這個迴圈是否存在,答案就是:
\[\sum_{p \text{ is permutation}} \left(\sum_{} x_i\right)^m \]根據
\[\left(\sum_{i=1}^{n} x_i\right)^m= \sum_{j=0}^{m} \begin{Bmatrix} m \\ j\end{Bmatrix}j! \dbinom{\sum x_i}{j} \]我們知道如果欽定 \(k\) 個迴圈出現,那麼就會貢獻 \(\begin{Bmatrix} m \\ k\end{Bmatrix} k!\)。顯然迴圈的數量太多了,沒有辦法列舉,怎麼辦?
非常巧妙的轉化:選出了 \(k\) 個迴圈後,新增一個垃圾節點 \(n+1\),把剩餘元素和 \(n+1\) 分在一起做一個迴圈,就可以映射出剩餘的所有迴圈方案。即最後答案就是:
\[\sum_{k=0}^{n} \begin{bmatrix} n + 1 \\k + 1\end{bmatrix} \begin{Bmatrix} m \\ k\end{Bmatrix} k! \]這一步轉化非常的奇怪,乍一想挺讓人無法理解的,但實際上可以這麼考慮:把每一個迴圈從小到大展開,把迴圈內的最大值移動到每個迴圈的第一個位置。再按每一個迴圈的最大值對迴圈排序,把 \(n+1\)
其實這個想法與 [FJOI2016]建築師 有些類似。
\(n\) 這麼大我們是不是要寫多項式呀?
然而 \(n\) 很大的時候後面的第二類斯特林數的值是 \(0\),不用求。
複雜度 \(O(nm+m^2)\)。
Code
#include <bits/stdc++.h> #define LL long long const LL MOD = 1e9 + 7; LL s[100000 + 23][302] ,S[302][302] ,fac[302]; void init(){ s[0][0] = 1; for(int i = 1 ; i <= 100001 ; ++i){ for(int j = 1 ; j <= 301 ; ++j){ s[i][j] = (s[i - 1][j] * (i - 1) + s[i - 1][j - 1]) % MOD; } } S[0][0] = 1; for(int i = 1 ; i <= 301 ; ++i){ for(int j = 1 ; j <= 301 ; ++j){ S[i][j] = (S[i - 1][j] * j + S[i - 1][j - 1]) % MOD; } } fac[0] = 1; for(int i = 1 ; i <= 301 ; ++i) fac[i] = fac[i - 1] * i % MOD; } class CyclesNumber{ private: LL solve(int n ,int m){ LL Ans = 0; for(int i = 0 ; i <= std::min(n ,m) ; ++i){ Ans = (Ans + s[n + 1][i + 1] * S[m][i] % MOD * fac[i]) % MOD; } return Ans; } public: CyclesNumber(){ init(); } std::vector<int> getExpectation(std::vector<int> n ,std::vector<int> m){ std::vector<int> Ans; for(int i = 0 ; i < n.size() ; ++i){ Ans.push_back(solve(n[i] ,m[i])); } return Ans; } }F; void output(std::vector<int> a){ for(auto i : a){ printf("%d\n" ,i); } } int main(){ output(F.getExpectation({10 ,20 ,30} ,{10 ,20 ,30})); }