题目
题目背景
唉!无力挽留春归去,春去难再聚。回首梧桐风无痕,只有叶犹存。
题目概要
在
G
F
(
p
)
\mathbb{GF}(p)
GF(p) 上找出两个
n
×
n
n{\times} n
n×n 矩阵
A
,
B
A,B
A,B 使得
det
(
B
)
≠
0
\text{det}(B)\ne 0
det(B)=0 即
B
B
B 满秩,且
A
=
B
×
A
A=B\times A
A=B×A 。所有运算在
G
F
(
p
)
\mathbb{GF}(p)
GF(p) 上进行。
请求出 ⟨ A , B ⟩ \langle A,B\rangle ⟨A,B⟩ 的数量,对 998244353 998244353 998244353 取模。
数据范围与提示
n
⩽
3
×
1
0
7
n\leqslant 3\times 10^7
n⩽3×107 而
p
⩽
1
0
9
p\leqslant 10^9
p⩽109 。保证
p
p
p 为质数。
思路
我的想法是,矩阵相同等价于
∀
v
\forall v
∀v 都有
A
×
v
=
(
B
×
A
)
×
v
A\times v=(B\times A)\times v
A×v=(B×A)×v,然后结合律一下得到
∀
v
∈
Im
(
A
)
,
v
=
B
×
v
\forall v\in\text{Im}(A),\;v=B\times v
∀v∈Im(A),v=B×v
再移项就是
Im
(
A
)
⫅
ker
(
B
−
I
)
\text{Im}(A)\subseteqq\ker(B-I)
Im(A)⫅ker(B−I)
上面的 I I I 是单位矩阵。容易发现,只需要知道 dim ker ( B − I ) \dim\ker(B-I) dimker(B−I) 即可求出 A A A 的数量。那么问题来了:在条件 dim ker ( B ) = 0 \dim\ker(B)=0 dimker(B)=0 下,怎么求 dim ker ( B − I ) \dim\ker(B-I) dimker(B−I) 的分布?😵
问题还是出在
det
(
B
)
≠
0
\text{det}(B)\ne 0
det(B)=0 上。出题人其实希望它构成 群。又因为
A
A
A 在
B
B
B 变化下是封闭的,所以
B
B
B 就是一种 置换群,我们其实要算 不动点 数量之和。利用
burnside
\text{burnside}
burnside 定理
∑
g
∈
G
∣
X
g
∣
=
∣
G
∣
⋅
∣
X
/
G
∣
\sum_{g\in G}|X^g|=|G|\cdot|X/G|
g∈G∑∣Xg∣=∣G∣⋅∣X/G∣
即不动点的数量之和 = = = 置换数 × \times × 等价类数。看上去很神奇;虽说不用 burnside \text{burnside} burnside 也完全可以推出下面的式子。本题的关键在于——下面我们会说到——等价类大小相同。我觉得它不那么容易发现,却应该可以理解:因为 张成空间相同的矩阵都是相似的。
置换数,就是合法的
B
B
B 的数量。考虑每个行向量,那么第
i
i
i 个行向量就不能与前
(
i
−
1
)
(i{\rm-}1)
(i−1) 个行向量线性相关;而前
(
i
−
1
)
(i{\rm-}1)
(i−1) 个行向量张成了
(
i
−
1
)
(i{\rm-}1)
(i−1) 维子空间,故该行向量的可选数量是
(
p
n
−
p
i
−
1
)
(p^n{-}p^{i-1})
(pn−pi−1),所以
∣
G
∣
=
∏
i
=
1
n
(
p
n
−
p
i
−
1
)
|G|=\prod_{i=1}^{n}(p^n-p^{i-1})
∣G∣=i=1∏n(pn−pi−1)
等价类数,则稍微复杂些。但是我们会震惊地发现,对于秩相同的 A A A,等价类大小相同。这个结论可以在下面的推导中发现。
设 rank ( A ) = k \text{rank}(A)=k rank(A)=k 。首先需要发现的是 rank ( B A ) = k \text{rank}(BA)=k rank(BA)=k,即这种 A A A 对于 B B B 变换是封闭的。只需考察方程 B A v = 0 BAv=0 BAv=0,因 ker ( B ) = { 0 } \ker(B)=\{0\} ker(B)={0},其唯一解是 A v = 0 Av=0 Av=0,故 ker ( B A ) = ker ( A ) \ker(BA)=\ker(A) ker(BA)=ker(A),证毕。
另一方面,我们可以摆脱 det ( B ) ≠ 0 \text{det}(B)\ne 0 det(B)=0 这个约束。即,对于 rank ( A ) = rank ( A ′ ) = k \text{rank}(A)=\text{rank}(A')=k rank(A)=rank(A′)=k,若存在任意矩阵 B 0 B_0 B0 使得 A ′ = B 0 A A'=B_0A A′=B0A,则存在满秩矩阵 B B B 使得 A ′ = B A A'=BA A′=BA 。毕竟 Im ( A ) \text{Im}(A) Im(A) 与 ker ( B 0 ) \ker(B_0) ker(B0) 必无交,将 ker ( B 0 ) \ker(B_0) ker(B0) 映射到 Im ( B 0 ) \text{Im}(B_0) Im(B0) 以外,就可以让秩扩充到满。
由于 B B B 可以交换 A A A 的两行,不妨规定 A A A 的前 k k k 行是线性无关的——其余情况都假定为 “非法”,不影响等价类数量。那么计算一下 A A A 的数量:与求 ∣ G ∣ |G| ∣G∣ 方法相同,应该是 ∏ i = 0 k − 1 ( p n − p i ) \prod_{i=0}^{k-1}(p^n-p^i) ∏i=0k−1(pn−pi),然后乘 σ = ( p k ) n − k \sigma=(p^k)^{n-k} σ=(pk)n−k 。这个 σ \sigma σ 不重要,等会儿我们会看到。
现在试着计算 A ′ = B A A'=BA A′=BA 的不同数量,即等价类的大小。 A ′ A' A′ 的每个行向量,都是 A A A 的行向量的线性和。以 A A A 的前 k k k 个行向量为基底,则 A ′ A' A′ 的每个行向量等价于一个 k k k 维向量。而 A ′ A' A′ 的前 k k k 个行向量就是 k k k 个线性无关 k k k 维向量。用求 ∣ G ∣ |G| ∣G∣ 的方法就能计算。其余行向量任选——注意,不是选线性变换 B B B 的系数,而是选 A ′ A' A′ 的行向量——方案数也是 σ \sigma σ,也就消掉了。你看,我们算出来,和 A A A 在同一个等价类内的元素数量是常数(与 A A A 无关)!
所以每个等价类大小都是
σ
∏
i
=
0
k
−
1
(
p
k
−
p
i
)
\sigma\prod_{i=0}^{k-1}(p^k-p^i)
σ∏i=0k−1(pk−pi),用总数除以它,就得到等价类数量。消掉
σ
\sigma
σ 得
∣
X
k
/
G
∣
=
∏
i
=
0
k
−
1
p
n
−
p
i
p
k
−
p
i
|X_k/G|=\prod_{i=0}^{k-1}\frac{p^n-p^i}{p^k-p^i}
∣Xk/G∣=i=0∏k−1pk−pipn−pi
求 ∣ X / G ∣ = ∑ k = 0 n ∣ X k / G ∣ |X/G|=\sum_{k=0}^{n}|X_k/G| ∣X/G∣=∑k=0n∣Xk/G∣ 也蛮简单。算 ∣ X k / G ∣ |X_k/G| ∣Xk/G∣ 时,分子分母分别算,都可以递推。最后离线求逆元,时间复杂度 O ( k + log p ) \mathcal O(k+\log p) O(k+logp) 。
代码
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cctype>
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long llong;
inline int readint(){
int a = 0, c = getchar(), f = 1;
for(; !isdigit(c); c=getchar())
if(c == '-') f = -f;
for(; isdigit(c); c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
const int MOD = 998244353;
inline int qkpow(llong b,int q){
llong a = 1;
for(; q; q>>=1,b=b*b%MOD)
if(q&1) a = a*b%MOD;
return int(a);
}
const int MAXN = 30000005;
int powp[MAXN], mom[MAXN], qzj[MAXN];
int main(){
int n = readint(), p = readint();
rep(i,mom[0]=qzj[0]=powp[0]=1,n){
powp[i] = int(llong(powp[i-1])*p%MOD);
mom[i] = int(llong(powp[i]-1)
*powp[i-1]%MOD*mom[i-1]%MOD);
qzj[i] = int(llong(qzj[i-1])*mom[i]%MOD);
}
int inv = qkpow(qzj[n],MOD-2);
drep(i,n,1){
const int t = mom[i];
mom[i] = int(llong(inv)*qzj[i-1]%MOD);
inv = int(llong(inv)*t%MOD);
}
int son = 1, ans = 1; // when i = 0
rep(i,1,n){
son = int(llong(powp[n]+MOD-powp[i-1])*son%MOD);
ans = int((ans+llong(son)*mom[i])%MOD);
}
ans = int(llong(ans)*son%MOD);
printf("%d\n",ans);
return 0;
}