题面
对于一个 N × N N\times N N×N 的矩阵, m i , j = gcd ( i , j ) k m_{i,j}=\gcd(i,j)^k mi,j=gcd(i,j)k
你的任务是计算这个矩阵的行列式
共 t ≤ 20 t\leq20 t≤20 组数据, 1 ≤ N ≤ 1 0 6 , 1 ≤ k ≤ 1 0 9 1\leq N\leq 10^6~,~1\leq k\leq10^9 1≤N≤106 , 1≤k≤109 。
题解
看这个数据范围,应该要我们达到预处理不超过 O ( N log N ) O(N\log N) O(NlogN) ,循环内不超过 O ( N ) O(N) O(N) 的复杂度。
如果做过 Find The Determinant( m i , j = g c d ( i , j ) m_{i,j}=gcd(i,j) mi,j=gcd(i,j)),应该知道,我们其实是利用了 n = ∑ i ∣ n ϕ ( i ) n=\sum_{i|n}\phi(i) n=∑i∣nϕ(i) ,然后将 M M M 拆成两个矩阵相乘: m i , j = g c d ( i , j ) = ∑ k ∣ i , k ∣ j ϕ ( k ) = ∑ k ( [ k ∣ i ] ϕ ( k ) ) ⋅ [ k ∣ j ] m_{i,j}=gcd(i,j)=\sum_{k|i,k|j}\phi(k)=\sum_{k}\Big([k|i]\phi(k)\Big)\cdot[k|j] mi,j=gcd(i,j)=∑k∣i,k∣jϕ(k)=∑k([k∣i]ϕ(k))⋅[k∣j] ,利用这两个矩阵都是三角矩阵从而直接累乘,得出最终行列式答案: ∏ ϕ ( i ) \prod\phi(i) ∏ϕ(i)。
这道题也是一样的思路,不过 n k n^k nk 又等于什么函数的狄利克雷卷积呢?
我们假设已经知道了这个函数,令
n
k
=
∑
i
∣
n
f
(
i
)
n^k=\sum_{i|n}f(i)
nk=∑i∣nf(i) ,那么
m
i
,
j
=
g
c
d
(
i
,
j
)
k
=
∑
k
∣
i
,
k
∣
j
f
(
k
)
=
∑
k
(
[
k
∣
i
]
f
(
k
)
)
⋅
[
k
∣
j
]
m_{i,j}=gcd(i,j)^k=\sum_{k|i,k|j}f(k)=\sum_{k}\Big([k|i]f(k)\Big)\cdot[k|j]
mi,j=gcd(i,j)k=k∣i,k∣j∑f(k)=k∑([k∣i]f(k))⋅[k∣j]
我们就可以得到,最终的答案是 ∏ i = 1 N f ( i ) \prod_{i=1}^Nf(i) ∏i=1Nf(i) 。
问题是现在不知道 f ( i ) f(i) f(i) 每个位置的函数值,我们需要求。
n k = ∑ i ∣ n f ( i ) n^k=\sum_{i|n}f(i) nk=∑i∣nf(i) 其实可以写成狄利克雷卷积的形式: i d k = f ∗ I id_k=f*I idk=f∗I 。
也就是说, f = i d k ∗ I − 1 f=id_k*I^{-1} f=idk∗I−1 ,我们可以求狄利克雷逆!
别急,我们只需要知道有狄利克雷逆这个东西就行了,然后可以现推:
I
−
1
∗
I
=
e
I
−
1
(
1
)
I
(
1
)
=
1
⇒
I
−
1
(
1
)
=
1
(
n
>
1
)
∑
i
∣
n
I
−
1
(
i
)
I
(
n
/
i
)
=
0
⇒
I
−
1
(
n
)
I
(
1
)
+
∑
i
∣
n
,
i
<
n
I
−
1
(
i
)
I
(
n
/
i
)
=
0
⇒
I
−
1
(
n
)
=
−
∑
i
∣
n
,
i
<
n
I
−
1
(
i
)
I
(
n
/
i
)
I^{-1}*I=e\\ I^{-1}(1)I(1)=1~\Rightarrow~I^{-1}(1)=1\\ (n>1)~~~\sum_{i|n}I^{-1}(i)I(n/i)=0\\ \Rightarrow I^{-1}(n)I(1)+\sum_{i|n,i<n}I^{-1}(i)I(n/i)=0\\ \Rightarrow I^{-1}(n)=-\sum_{i|n,i<n}I^{-1}(i)I(n/i)
I−1∗I=eI−1(1)I(1)=1 ⇒ I−1(1)=1(n>1) i∣n∑I−1(i)I(n/i)=0⇒I−1(n)I(1)+i∣n,i<n∑I−1(i)I(n/i)=0⇒I−1(n)=−i∣n,i<n∑I−1(i)I(n/i)
可以用埃氏筛法模拟这个递推过程,时间复杂度 O ( N log N ) O(N\log N) O(NlogN) ,由于 I − 1 I^{-1} I−1 并不随着 N N N 和 k k k 变化,因此我们可以在所有询问之前预处理出来 I − 1 ( 1 ∼ 1 0 6 ) I^{-1}(1\sim10^6) I−1(1∼106)。
接下来我们需要线性时间复杂度求出每个 f ( i ) = ∑ j ∣ i i d k ( j ) I − 1 ( i / j ) f(i)=\sum_{j|i}id_k(j)I^{-1}(i/j) f(i)=∑j∣iidk(j)I−1(i/j) 。
这里有个非常好理解但是证明稍麻烦的结论:积性函数 g g g 的狄利克雷逆 g − 1 g^{-1} g−1 也是积性函数。
所以, I − 1 I^{-1} I−1 是积性函数,进而 f f f 也是积性函数。 f f f 是积性函数,我们便可以思考欧拉筛。
由于 i d k id_k idk 完全积性,我们对于每个质数 p p p 用快速幂求出 i d k ( p ) id_k(p) idk(p),再欧拉筛可以求出所有 i d k id_k idk 。再对于每个 p w p^w pw(质数的幂)枚举因数( 1 , p , p 2 , . . . , p w 1,p,p^2,...,p^w 1,p,p2,...,pw,无需根号)求出 f ( p w ) f(p^w) f(pw) ,进而欧拉筛求出所有 f f f 。时间复杂度 O ( N log k ln N ) ≈ O ( N ) O(\frac{N\log k}{\ln N})\approx O(N) O(lnNNlogk)≈O(N) 。
具体实现上,我们可以欧筛的时候求每个数最小质因子的乘积,用于判断是否是质数幂,以及用于求积性函数。
CODE
注意,这个屑OJ限制了代码长度
#include<map>
#include<set>
#include<cmath>
#include<queue>
#include<stack>
#include<random>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 1000005
#define LL long long
#define ULL unsigned long long
#define ENDL putchar('\n')
#define DB long double
#define lowbit(x) (-(x) & (x))
#define FI first
#define SE second
LL read() {
LL f = 1,x = 0;int s = getchar();
while(s < '0' || s > '9') {if(s<0)return -1;if(s=='-')f=-f;s = getchar();}
while(s >= '0' && s <= '9') {x = (x<<1) + (x<<3) + (s^48);s = getchar();}
return f*x;
}
const int MOD = 1000003;
int n,m,s,o,k;
int iv[MAXN];
int p[MAXN],f[MAXN],cnt,f1[MAXN],ls[MAXN];
int fn[MAXN];
int qkpow(int a,int b) {
int res = 1; while(b > 0) {
if(b & 1) res = res *1ll* a % MOD;
a = a *1ll* a % MOD; b >>= 1;
}return res;
}
void sieve(int n) {
f1[1] = 1; fn[1] = 1; cnt = 0;
for(int i = 1;i <= n;i ++) f[i] = 0;
for(int i = 2;i <= n;i ++) {
if(!f[i]) p[++ cnt] = i,f1[i] = qkpow(i,k),fn[i] = (f1[i] + iv[i]) % MOD,ls[i] = i;
for(int j = 1;j <= cnt && i*p[j] <= n;j ++) {
int y = i*p[j];
f[y] = 1;
f1[y] = f1[i]*1ll*f1[p[j]] % MOD;
if(i % p[j] == 0) {
ls[y] = ls[i]*p[j];
fn[y] = 0;
if(y == ls[y]) {
for(int pw = 1;pw <= ls[y];pw *= p[j]) (fn[y] += f1[pw]*1ll*iv[y/pw]%MOD) %= MOD;
}
else fn[y] = fn[y/ls[y]]*1ll*fn[ls[y]] % MOD;
break;
}
ls[y] = p[j];
fn[y] = fn[i]*1ll*fn[p[j]] % MOD;
}
}return ;
}
int main() {
iv[1] = 1;
for(int i = 1;i <= MAXN-5;i ++) {
for(int j = 2;i*j <= MAXN-5;j ++) {
(iv[i*j] += MOD - iv[i]) %= MOD;
}
}
int T = read();
while(T --) {
n = read(); k = read();
sieve(n);
int ans = 1;
for(int i = 1;i <= n;i ++) {
ans = ans *1ll* fn[i] % MOD;
}
printf("%lld\n",ans);
}
return 0;
}