【BZOJ5481】矩阵(组合数学)

传送门


解析:

乍一看并不好发现递推式,我们需要巧妙的转化。

我们将每一个 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=2n(in)(i1n1)Aifni

枚举有多少个行与第一行在同一环中,这 i i i个环占了哪些列,删去这 i i i i i i列之后,剩下的就是一个 ( n − i ) × ( n − i ) (n-i)\times (n-i) (ni)×(ni)的矩阵,就可以得到上面的式子。

现在考虑这个东西: A i A_i Ai怎么求。

考虑将原来的矩阵行列分别化为点,那么最后的连成的就是一个两边各有 n n n个点的单环二分图。

首先匹配一波,对于左边的每个点选择右边尚未匹配的点,这一步的方案数就是 n ! n! n!

然后进行第二次匹配,我们找到左 1 1 1号点的右匹配点,在左边任意选择一个尚未进行二次匹配且不是 1 1 1号的点进行第二次匹配,对匹配到的点的第一次匹配点,我们重复上面的过程。注意,由于要求是一个单环,所以不能在最后一个点之前匹配左 1 1 1号点。这一部分的方案数就是 ( n − 1 ) ! (n-1)! (n1)!

我们发现这个过程的逆过程也被计算了一次,所以需要除以 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=2ni!(ni)!n!(i1)!(ni)!(n1)!2i!(i1)!fni

化简得到:

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!(n1)!i=2n((ni)!)2fni

发现两边似乎有长得很像的结构:

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=2n((ni)!)2fni

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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值