[SDOI2017]数字表格

发布时间 2023-06-02 12:35:16作者: A_Big_Jiong

题意

求如下表达式的值

\[\prod_{i=1}^{n} \prod_{j=1}^{m} f_{gcd(i,j)} \pmod{10^9 + 7} \]

其中,\(f_i\)为 fibonacci 数列的第\(i\)项,\(n, m \leqslant 10^6\)

Solution

\[\prod_{i=1}^{n} \prod_{j=1}^{m} f_{gcd(i,j)} \]

改变枚举顺序,优先枚举\(d = gcd(i,j)\)

\[=\prod_{d=1}^{min\{n, m\}} f_d ^ {\sum_{i=1}^{\lfloor \frac{n}{d} \rfloor} \sum_{j=1}^{\lfloor \frac{m}{d} \rfloor} [gcd(i,j)=1]} \]

取出指数,记:

\[g(d) = \sum_{i=1}^{\lfloor \frac{n}{d} \rfloor} \sum_{j=1}^{\lfloor \frac{m}{d} \rfloor} [gcd(i,j)=1] \]

\[=\sum_{i=1}^{\lfloor \frac{n}{d} \rfloor} \sum_{j=1}^{\lfloor \frac{m}{d} \rfloor} \sum_{x|gcd(i,j)} \mu(x) \]

莫比乌斯反演经典处理手段,优先枚举因子,

\[= \sum_{x=1}^{min\{\lfloor \frac{n}{d} \rfloor , \lfloor \frac{m}{d} \rfloor \}} \mu(d) \lfloor \frac{n}{xd} \rfloor \lfloor \frac{m}{xd} \rfloor \]

带回原表达式,则有,

\[\prod_{d=1}^{min\{n, m\}} \ \prod_{x=1}^{min\{\lfloor \frac{n}{d} \rfloor, \lfloor \frac{m}{d}\ \rfloor\}}f_d^{\mu(x) \lfloor \frac{n}{x\ d} \rfloor \lfloor \frac{m}{x\ d} \rfloor} \]

可以比较容易发现,该式子可以通过两次数论分块解决,时间复杂度为\(O(T \ n \log n)\),还需要继续优化

考虑将此式子转化为更加便于预处理\(\mu(x)\)的表达式,则记\(T = xd\)

\[= \prod_{T=1}^{min\{n, m\}} \ \prod_{d|T} f_d^{\mu(\lfloor \frac{T}{d} \rfloor) \lfloor \frac{n}{T} \rfloor \lfloor \frac{m}{T} \rfloor} \]

改变一下幂指数的顺序,

\[= \prod_{T=1}^{min\{n, m\}} \left( \prod_{d|T} f_d^{\mu(\lfloor \frac{T}{d} \rfloor)} \right)^{\lfloor \frac{n}{T} \rfloor \lfloor \frac{m}{T} \rfloor} \]

外面是我们熟悉的数论分块的形式,里面考虑到\(\mu(\lfloor \frac{T}{d} \rfloor)\)的取值只能为\(-1, 1, 0\),可以\(O(n \log {n})\)筛出\(\mu\)的值并且计算,然后在\(O(n \sqrt{n})\)的时间内完成。

\(O(n \sqrt{n})\)?感觉在\(10^6\)下不能容纳这个时间复杂度,但是考虑到\(\mu(i) \ne 0\)的个数只有\(607926\)个,时间复杂度应该重新表达为\(O(n + 607926\sqrt{n})\)

费马小定理与欧拉定理

费马小定理

\(p\)为质数,\(gcd(a,p)=1\),则\(a^{p-1} \equiv 1 \pmod{p}\)

欧拉定理

\(gcd(a,m)=1\)\(a^{\varphi(m)} \equiv 1 \pmod{m}\)

拓展欧拉定理

\[a^b \equiv \begin{cases} a^{b \bmod \varphi(m)}, & \text {$gcd(a,m)=1,$} \\ a^b, &\text {$gcd(a,m) \ne 1, b < \varphi(m),$} \\ a^{(b \bmod \varphi(m)) + \varphi(m)} , &\text {$gcd(a,m) \ne 1, b \geqslant \varphi(m).$} \end{cases} \pmod{m}\]

以上证明略。

因为本题要求对幂指数取模求解,并且本题的模数$m = 10^9 + 7 $,可以通过拓展欧拉定理加以求解。

Code

点击查看代码
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>

using namespace std;

const int N = 1e6 + 50;
const int mod = 1e9 + 7;

inline int read() {
	int res = 0, f = 1;
	char c = getchar();
	while (c > '9' || c < '0') {
		if (c == '-')  f = -1;
		c = getchar();
	}
	while (c >= '0' && c <= '9') {
		res = (res << 3) + (res << 1) + c - '0';
		c = getchar();
	}	
	return res * f;
}

inline int add_mod(int a, int b) {
	a += b;
	if (a < 0)  a += mod;
	if (a >= mod)  a -= mod;
	return a;
}
inline int del_mod(int a, int b) {
	a -= b;
	if (a < 0)  a += mod;
	if (a >= mod)  a -= mod;
	return a;
}
inline int mul_mod(int a, int b) {
	a = 1ll * a * b % mod;
	return a;
}

int num;
int prime[N], mu[N];
int v[N];

int f[N], F[N], inv[N];

inline int qpow(int a, int b) {
	int base = 1;
	while (b) {
		if (b & 1)  base = mul_mod(base, a);
		a = mul_mod(a, a);
		b >>= 1;
	}
	return base;
}

void init() {
	int MAX = 1e6;
	F[0] = F[1] = f[1] = 1;
	inv[0] = inv[1] = 1;
	mu[1] = 1;
	for (int i = 2; i <= MAX; ++i) {
		f[i] = add_mod(f[i - 1], f[i - 2]);
		inv[i] = qpow(f[i], mod - 2);
		F[i] = 1;
		if (!v[i]) {
			prime[++num] = i;
			v[i] = i;
			mu[i] = -1;
		}
		for (int j = 1; j <= num; ++j) {
			if (prime[j] > v[i] || prime[j] > MAX / i)  break;
			v[i * prime[j]] = prime[j];
			if (i % prime[j] == 0) {
				mu[i * prime[j]] = 0;
				break;
			}
			mu[i * prime[j]] = -mu[i];
		}
	}
	for (int i = 1; i <= MAX; ++i) {
		if (!mu[i])   continue;
		for (int j = i; j <= MAX; j += i) {
			F[j] = mul_mod(F[j], (mu[i] == 1 ? f[j / i] : inv[j / i]));
		}
	}
	for (int i = 2; i <= MAX; ++i)   F[i] = mul_mod(F[i], F[i - 1]);
}

int n, m;
int ans;

int main () {
	init();
	int T = read();
	while (T--) {
		ans = 1;
		n = read(), m = read();
		for (int i = 1, j; i <= min(n, m); i = j + 1) {
			j = min(n / (n / i), m / (m / i));
			int tmp = mul_mod(F[j], qpow(F[i - 1], mod - 2));
			ans = mul_mod(ans, qpow(tmp, 1ll * (n / i) * (m / i) % (mod - 1)));
		}
		printf("%d\n", ans);
	}
    return 0;
}