0%

「2019夏令营提高组」数论线代全家桶

题目链接

题意

已知 n×nn\times n 矩阵 AA 满足 Ai,j=ijgcd(i,j)A_{i,j}=ij\cdot\gcd(i,j),求 det(A)\det(A)

多组询问。

思路

显然可以改为求 Xi,j=gcd(i,j)X_{i,j}=\gcd(i,j)det(X)\det(X),最后再将答案乘上 i=1ni2\prod_{i=1}^ni^2 即可。

暴力高斯消元可以得到下面的上三角矩阵:

det(X)=det([111111010101002002000200000040000002])\det(X)=\det\left( \begin{bmatrix} 1&1&1&1&1&1&\cdots\\ 0&1&0&1&0&1&\cdots\\ 0&0&2&0&0&2&\cdots\\ 0&0&0&2&0&0&\cdots\\ 0&0&0&0&4&0&\cdots\\ 0&0&0&0&0&2&\cdots\\ \vdots&\vdots&\vdots&\vdots&\vdots&\vdots&\ddots \end{bmatrix} \right)

可以发现主对角线上排列着欧拉函数,所以可以线筛出欧拉函数得到 AC。

但是如何证明?

EI 给出了线性代数的方法。

X×U=VX\times U=V,则对于向量 ViV_i

Vi=jUjgcd(i,j)=jUjdgcd(i,j)φ(d)=di(φ(d)djUj)\begin{aligned} V_i&=\sum_jU_j\cdot\gcd(i,j)\\ &=\sum_jU_j\sum_{d|\gcd(i,j)}\varphi(d)\\ &=\sum_{d|i}\left(\varphi(d)\cdot\sum_{d|j}U_j\right) \end{aligned}

f(i)=φ(i)ijUjf(i)=\varphi(i)\cdot\sum_{i|j}U_jg(i)=ijUjg(i)=\sum_{i|j}U_j,则:

Vi=dif(d)f(i)=φ(i)g(i)g(i)=ijUj\begin{aligned} V_i&=\sum_{d|i}f(d)\\ f(i)&=\varphi(i)\cdot g(i)\\ g(i)&=\sum_{i|j}U_j \end{aligned}

对矩阵分步进行乘法,即把 XX 看成三个矩阵的乘积。

根据行列式的性质,UgU\to gfVf\to V 时行列式并没有变化(因为都是向量之间的加减)。
gfg\to f 时,第 ii 个向量乘了 φ(i)\varphi(i),所以行列式增大到原来的 iφ(i)\prod_i\varphi(i) 倍了。

实现

预处理答案即可。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include<cstdio>
#include<vector>
using namespace std;
const int N=1e7;
int t,p,n;
int phi[N+1];
int res[N+1];
void initPhi()
{
static bool npri[N+1];
static vector<int> pri;
phi[1]=1;
for(int i=2;i<=N;++i) {
if(!npri[i]) {
pri.push_back(i),phi[i]=i-1;
}
for(int j:pri) {
int prod=i*j;
if(prod>N) {
break;
}
npri[prod]=true;
if(i%j==0) {
phi[prod]=phi[i]*j;
break;
} else {
phi[prod]=phi[i]*phi[j];
}
}
}
return;
}
int main()
{
freopen("all.in","r",stdin);
freopen("all.out","w",stdout);
scanf("%d%d",&t,&p);
initPhi();
res[0]=1;
for(int i=1;i<=N;++i) {
res[i]=1ll*res[i-1]*phi[i]%p*i%p*i%p;
}
while(t--) {
scanf("%d",&n);
printf("%d\n",res[n]);
}
return 0;
}

致谢

感谢 EI 给出了精妙证明。
感谢 PinkRabbit 对式子的解释。