传送门
解析:
乍一看并不好发现递推式,我们需要巧妙的转化。
我们将每一个 1 1 1视作是行列之间的连边,于是我们发现每一个方案对应的是若干个环的划分。
所以我们考虑对这个矩阵的若干个点进行划分。
考虑枚举枚举第一行所在的环的状态(即行和列),令 A i A_i Ai表示 i × i i\times i i×i的矩阵中只有一个环的状态数,则我们有如下递推式:
f n = ∑ i = 2 n ( n i ) ( n − 1 i − 1 ) A i f n − i f_n=\sum_{i=2}^{n}{n\choose i}{n-1\choose i-1}A_if_{n-i} fn=i=2∑n(in)(i−1n−1)Aifn−i
枚举有多少个行与第一行在同一环中,这 i i i个环占了哪些列,删去这 i i i行 i i i列之后,剩下的就是一个 ( n − i ) × ( n − i ) (n-i)\times (n-i) (n−i)×(n−i)的矩阵,就可以得到上面的式子。
现在考虑这个东西: A i A_i Ai怎么求。
考虑将原来的矩阵行列分别化为点,那么最后的连成的就是一个两边各有 n n n个点的单环二分图。
首先匹配一波,对于左边的每个点选择右边尚未匹配的点,这一步的方案数就是 n ! n! n!。
然后进行第二次匹配,我们找到左 1 1 1号点的右匹配点,在左边任意选择一个尚未进行二次匹配且不是 1 1 1号的点进行第二次匹配,对匹配到的点的第一次匹配点,我们重复上面的过程。注意,由于要求是一个单环,所以不能在最后一个点之前匹配左 1 1 1号点。这一部分的方案数就是 ( n − 1 ) ! (n-1)! (n−1)!
我们发现这个过程的逆过程也被计算了一次,所以需要除以 2 2 2。
但是这个式子仍然不能方便的维护前缀和,考虑进一步转化。
将原式的组合数拆成阶乘:
f n = ∑ i = 2 n n ! i ! ( n − i ) ! ⋅ ( n − 1 ) ! ( i − 1 ) ! ( n − i ) ! i ! ( i − 1 ) ! 2 f n − i f_n=\sum_{i=2}^{n}\frac{n!}{i!(n-i)!}\cdot\frac{(n-1)!}{(i-1)!(n-i)!}\frac{i!(i-1)!}{2}f_{n-i} fn=i=2∑ni!(n−i)!n!⋅(i−1)!(n−i)!(n−1)!2i!(i−1)!fn−i
化简得到:
f n = n ! ( n − 1 ) ! 2 ∑ i = 2 n f n − i ( ( n − i ) ! ) 2 f_n=\frac{n!(n-1)!}{2}\sum_{i=2}^n\frac{f_{n-i}}{((n-i)!)^2} fn=2n!(n−1)!i=2∑n((n−i)!)2fn−i
发现两边似乎有长得很像的结构:
f n ( n ! ) 2 = 1 2 n ∑ i = 2 n f n − i ( ( n − i ) ! ) 2 \frac{f_n}{(n!)^2}=\frac{1}{2n}\sum_{i=2}^{n}\frac{f_{n-i}}{((n-i)!)^2} (n!)2fn=2n1i=2∑n((n−i)!)2fn−i
设 g n = f n ( n ! ) 2 g_n=\frac{f_n}{(n!)^2} gn=(n!)2fn就可以愉快的转移了。
逆元线性推一下就行了。
代码:
#include<bits/stdc++.h>
#define ll long long
#define re register
#define cs const
cs int mod=998244353;
inline int add(int a,int b){return a+b>=mod?a+b-mod:a+b;}
inline int dec(int a,int b){return a<b?a-b+mod:a-b;}
inline int mul(int a,int b){return (ll)a*b%mod;}
cs int N=1e7+7;
int inv[N],inv2;
int n,g,gsum,glast,ans,fac;
signed main(){
std::cin>>n;
inv[0]=inv[1]=1;
for(int re i=2;i<=n;++i)inv[i]=mul(mod-mod/i,inv[mod%i]);
inv2=inv[2];
gsum=fac=1;
for(int re i=2;i<=n;++i){
fac=mul(fac,i);
g=mul(mul(inv2,inv[i]),gsum);
ans=add(ans,mul(g,mul(fac,fac)));
gsum=add(gsum,glast);
glast=g;
}
std::cout<<ans<<"\n";
return 0;
}