【SP1772 DETER2】Find The Determinant II (线性代数,狄利克雷逆)

该博客探讨了一种处理特定矩阵行列式计算的方法,适用于数据范围较大的情况。通过狄利克雷卷积和预处理狄利克雷逆,实现了O(NlogN)的时间复杂度求解,同时利用积性函数的性质进行线性时间复杂度的计算。文章详细介绍了算法的实现过程,包括欧拉筛和快速幂等技术,并提供了C++代码示例。
摘要由CSDN通过智能技术生成

题面

对于一个 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 t20 组数据, 1 ≤ N ≤ 1 0 6   ,   1 ≤ k ≤ 1 0 9 1\leq N\leq 10^6~,~1\leq k\leq10^9 1N106 , 1k109

题解

看这个数据范围,应该要我们达到预处理不超过 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=inϕ(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)=ki,kjϕ(k)=k([ki]ϕ(k))[kj] ,利用这两个矩阵都是三角矩阵从而直接累乘,得出最终行列式答案: ∏ ϕ ( i ) \prod\phi(i) ϕ(i)

这道题也是一样的思路,不过 n k n^k nk 又等于什么函数的狄利克雷卷积呢?

我们假设已经知道了这个函数,令 n k = ∑ i ∣ n f ( i ) n^k=\sum_{i|n}f(i) nk=inf(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=ki,kjf(k)=k([ki]f(k))[kj]

我们就可以得到,最终的答案是 ∏ 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=inf(i) 其实可以写成狄利克雷卷积的形式: i d k = f ∗ I id_k=f*I idk=fI

也就是说, f = i d k ∗ I − 1 f=id_k*I^{-1} f=idkI1 ,我们可以求狄利克雷逆

别急,我们只需要知道有狄利克雷逆这个东西就行了,然后可以现推:
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) I1I=eI1(1)I(1)=1  I1(1)=1(n>1)   inI1(i)I(n/i)=0I1(n)I(1)+in,i<nI1(i)I(n/i)=0I1(n)=in,i<nI1(i)I(n/i)

可以用埃氏筛法模拟这个递推过程,时间复杂度 O ( N log ⁡ N ) O(N\log N) O(NlogN) ,由于 I − 1 I^{-1} I1 并不随着 N N N k k k 变化,因此我们可以在所有询问之前预处理出来 I − 1 ( 1 ∼ 1 0 6 ) I^{-1}(1\sim10^6) I1(1106)


接下来我们需要线性时间复杂度求出每个 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)=jiidk(j)I1(i/j)

这里有个非常好理解但是证明稍麻烦的结论:积性函数 g g g 的狄利克雷逆 g − 1 g^{-1} g1 也是积性函数。

所以, I − 1 I^{-1} I1 是积性函数,进而 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值