基于值域预处理的快速 GCD
题目链接:luogu P5435
题目大意
要你 O(1) 求两个数的 gcd,其中值域是 1~1e6。
思路
考虑把要求的数分成若干个部分乘起来的结果。
然后这些部分都是小于
值
域
\sqrt{_{值域}}
值域 的(除了大质数),然后我们可以通过下面的方法分出三个。
(不过是大质数也无所谓,它是质数就也可以搞)
那接着考虑怎么分出来,这里先给出方法,就你欧拉筛的时候把枚举到的最小的质数乘在那个分好的三个中最小的那个,然后重新排序。
然后这里给证明:(证明为什么不会超过
值
域
\sqrt{_{值域}}
值域)
我们这里让
n
n
n 是要分解的数,
p
p
p 是最小的质数
首先显然
n
p
\dfrac{n}{p}
pn 的分解中最小的
a
a
a 是不大于
n
p
3
\sqrt[3]{\frac{n}{p}}
3pn 的(规定)。
然后
a
∗
p
⩽
n
p
3
∗
p
a*p\leqslant\sqrt[3]{\frac{n}{p}}*p
a∗p⩽3pn∗p
当
p
>
n
4
p>\sqrt[4]{n}
p>4n 的时候,很明显分解出的三个是它的三个质因子,一定是对的。
否则化一下式子:
a
∗
p
⩽
n
p
3
∗
p
a*p\leqslant\sqrt[3]{\frac{n}{p}}*p
a∗p⩽3pn∗p
a
∗
p
⩽
n
n
4
3
∗
n
4
=
n
3
∗
1
n
4
3
∗
(
n
4
3
)
3
=
n
3
∗
(
n
4
3
)
2
=
n
n
3
=
n
2
n
3
=
n
a*p\leqslant\sqrt[3]{\frac{n}{\sqrt[4]{n}}}*\sqrt[4]{n}=\sqrt[3]{n}*\frac{1}{\sqrt[3]{\sqrt[4]{n}}}*(\sqrt[3]{\sqrt[4]{n}})^3=\sqrt[3]{n}*(\sqrt[3]{\sqrt[4]{n}})^2=\sqrt[3]{n\sqrt{n}}=\sqrt[3]{\sqrt{n}^2\sqrt{n}}=\sqrt{n}
a∗p⩽34nn∗4n=3n∗34n1∗(34n)3=3n∗(34n)2=3nn=3n2n=n
然后就证出来了。
然后就好啦。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
#define mo 998244353
using namespace std;
int n, a[5001], b[5001];
int pr[1000001][4], prime[500001];
int gcd[1001][1001];
ll ans, di;
int GCD(int x, int y) {
int re = 1, noww;
for (int i = 0; i <= 2; i++) {
if (pr[x][i] <= 1000) {
noww = gcd[pr[x][i]][y % pr[x][i]];
}
else {//大于的部分只可能是值域,直接要么倍数是本身,要么是 1
if (y % pr[x][i] == 0) noww = pr[x][i];
else noww = 1;
}
y /= noww;
re *= noww;
}
return re;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
for (int i = 1; i <= n; i++) scanf("%d", &b[i]);
pr[1][0] = pr[1][1] = pr[1][2] = 1;//拆解
for (int i = 2; i <= 1000000; i++) {
if (!pr[i][0]) {
pr[i][0] = pr[i][1] = 1; pr[i][2] = i;
prime[++prime[0]] = i;
}
for (int j = 1; j <= prime[0] && i * prime[j] <= 1000000; j++) {
memcpy(pr[i * prime[j]], pr[i], sizeof(pr[i * prime[j]]));
pr[i * prime[j]][0] *= prime[j];
sort(pr[i * prime[j]], pr[i * prime[j]] + 3);
}
}
for (int i = 1; i <= 1000; i++) gcd[i][0] = gcd[0][i] = i;
for (int i = 1; i <= 1000; i++)//预处理根号值域内两两的 gcd
for (int j = 1; j <= 1000; j++) {
if (i > j) gcd[i][j] = gcd[j][i % j];
else gcd[i][j] = gcd[i][j % i];
}
for (int i = 1; i <= n; i++) {
di = 1; ans = 0;
for (int j = 1; j <= n; j++) {
di = di * i % mo;
ans = (ans + 1ll * di * GCD(a[i], b[j]) % mo) % mo;
}
printf("%lld\n", ans);
}
return 0;
}