题解 P5825 【排列计数】

皎月半洒花

2019-12-17 09:06:03

Solution

### 题意 我们记一个排列$P$的升高为$k$当且仅当存在$k$个位置$i$使得$P_i<P_{i+1}$ 现在给定排列长度$n$,对于所有整数$k\in [0,n]$求有多少个排列的升高为$k$。 $n\leq 200,000$ ### $\rm Sol$ 首先写一个递推式,令$f(n,k)$表示答案,则有 $$ f(n,k)=(n-k) \cdot f(n-1,k-1) + (k + 1) \cdot f(n-1,k) $$ 其意义是,考虑将这个序列的升高与圆排列的升高之间建立映射。定义顺时针为正方向(其实顺时针逆时针是对称的)。 * 一个长为$n$、升高为$k$的**圆排列**,有$n-k$种断开的方式,使之变成一个长为$n$,升高为$k$的**序列**; * 一个长为$n$,升高为$k+1$的**圆排列**,有$k+1$种断开方式,使之变成一个长为$n$,升高为$k$的**序列**。 * 一个长为$n$,升高为$k$的**圆排列**,删除掉值为$n$的那一项,则一定会删除掉恰好$1$个升高,所以对应一个长为$n-1$,升高为$k-1$的**序列**。 * 一个长为$n$,升高为$k+1$的**圆排列**,删除掉值为$n$的那一项,则一定会删除掉恰好$1$个升高,所以对应一个长为$n-1$,升高为$k$的**序列**。 显然后面两条的逆操作同样成立,所以是双射。 然而其实后面两条是等价的,只是平移了一下$k$…233 ~~然后就可以获得一个$n^2$算法,得到$0pts$的好成绩了~~ _____ 之后就需要用到黑宝书—— ~~《混凝土数学》~~ 《具体数学》了。 具体数学的$6.2$提到了这个数列(表),有一个公式是长这样的: $$ f(n,m)=\sum _{k=0}^{m}\binom{n+1}{k}(m+1-k)^n(-1)^k $$ 然而~~混凝土~~编者当时都没有给出这东西是怎么来的,~~并且咱也不怎么会推~~ ,于是或许就可以当个结论记住? 不过如果要是证明它是对的倒挺简单……即考虑把上式记作这个数列**关于$\bold n$的$\bold m$展开**,那么大家手算一下发现这东西跟关于$n-1$的$m-1$展开和关于$n-1$的$m$展开,存在递推形式,且递推形式和一开始给出的递推式相同;加之初值一样,所以成立。 ……然后就是计算生成函数,考虑如何计算第$n$行的$\bold {OGF}$ 通过观察法,可以发现作为第$m$项系数的这个和式,其中$\sum _{k=0}^{m}\binom{n+1}{k}(-1)^k$是一个二项式的展开,再将$(m+1-k)^n$这东西对称处理一下,~~通过简单的结合律~~ 就可以得到它的$\bold{OGF}$: $$ (1-x)^{n+1}\sum_{k=0}^{n} (k+1)^nx^{k} $$ ~~上述过程完全可以去OEIS上找,可以简化一系列猜结论+验证结论的过程~~ 然后就是一个多项式快速幂了…… 然而朴素的多项式快速幂是$\log^2$,~~再加上NTT本身>O(log n)的常数~~发现并不可以过……但实际上$(1-x)^n$状物就是个杨辉三角,于是组合数算一下就完了.jpg (截止到我写下这一行字为止,我的code还是最快的) ```cpp int main(){ cin >> PL ; K = PL += 1 ; int i, j ; for (i = 0 ; i < 19 ;++ i){ rr int *rua = gg[i] ; rua[0] = 1 ; rr int gi = rua[1] = expow(3, 998244352/(1 << (i + 1))) ; for (j = 2 ; j < (1 << i) ; ++ j) rua[j] = 1ll * rua[j - 1] * gi % P ; } M = 1 ; while (M <= (PL << 1)) M <<= 1, ++ l ; for (i = 0 ; i < M ; ++ i) R[i] = (R[i >> 1] >> 1) | ((i & 1) << (l - 1)) ; F[0] = 1, F[1] = -1, A[0] = 1, Fac[0] = Inv[0] = 1 ; for (i = 1 ; i <= PL ; ++ i) Fac[i] = 1ll * Fac[i - 1] * i % P ; Inv[PL] = expow(qwq = Fac[PL], P - 2) ; for (i = PL - 1 ; i ; -- i) Inv[i] = 1ll * Inv[i + 1] * (i + 1) % P ; for (i = 0 ; i <= PL ; ++ i) A[i] = (i & 1) ? P - Comb(i) : Comb(i) ; for (i = 0 ; i <= PL ; ++ i) S[i] = expow(i + 1, PL - 1) ; NTT(A, M, 1), NTT(S, M, 1) ; for (i = 0 ; i <= M ; ++ i) S[i] = 1ll * S[i] * A[i] % P ; NTT(S, M, -1) ; for (i = 0 ; i < PL ; ++ i) printf("%d ", S[i]) ; return 0 ; } ``` 讲道理,最后是不是应该%一下出题人啊,%%%$\sf{\color{black}{K}\color{red}{arry5307}}$