P5176 公约数
题意
有 T 组数据,每组数据给出,n,m,p,求:
i=1∑nj=1∑mk=1∑pgcd(i⋅j,i⋅k,j⋅k)×gcd(i,j,k)×(gcd(i,k)×gcd(j,k)gcd(i,j)+gcd(i,j)×gcd(j,k)gcd(i,k)+gcd(i,j)×gcd(i,k)gcd(j,k))
答案对 109+7 取模。
数据范围:107⩽n,m,p⩽2×107
思路
首先要看出来一个恒等式:
gcd(ij,ik,jk)=gcd(i,j,k)gcd(i,j)⋅gcd(j,k)⋅gcd(i,k)
证明:
该问题可以转化为讨论一个素因数的情况。
设 i,j,k 分别含有因数 pa,pb,pc,不妨令 a⩽b⩽c。
则对于该素数 p, gcd(ij,ik,jk)=min(a+b,b+c,a+c)=a+b
gcd(i,j,k)gcd(i,j)gcd(j,k)gcd(i,k)=min(a,b)+min(b,c)+min(a,c)−min(a,b,c)=a+b+a−a=a+b
不难发现恒等式:min(a+b,b+c,a+c)=min(a,b)+min(b,c)+min(a,c)−min(a,b,c)
解释:左式的含义是将 a,b,c 三者中最小的两者加起来,最小的两个数又有如下的表示法:
- 最小的数可以被表示为 min(a,b,c)
- 第二小的数可以表示为 min(a,b)+min(b,c)+min(a,c)−2min(a,b,c)
于是将再用这两种表示法加起来即得右式。
QED
于是,对原式进行变形:
原式=i=1∑nj=1∑mk=1∑pgcd(i,j)2+gcd(i,k)2+gcd(j,k)2=pi=1∑nj=1∑mgcd(i,j)2+mi=1∑nk=1∑pgcd(i,k)2+nj=1∑mk=1∑pgcd(j,k)2令f(n,m)=i=1∑nj=1∑mgcd(i,j)2原式=p⋅f(n,m)+m⋅f(n,p)+n⋅f(m,p)
问题转换为求解 g(n,m)。
g(n,m)=i=1∑nj=1∑mgcd(i,j)2=d=1∑min(n,m)i=1∑nj=1∑m[gcd(i,j)=d]⋅d2=d=1∑min(n,m)d2i=1∑⌊dn⌋j=1∑⌊dm⌋[gcd(i,j)=1]=d=1∑min(n,m)d2i=1∑⌊dn⌋j=1∑⌊dm⌋t∣gcd(i,j)∑μ(t)=d=1∑min(n,m)d2t=1∑min(⌊dn⌋,⌊dm⌋)μ(t)i=1∑⌊dtn⌋j=1∑⌊dtm⌋1=d=1∑min(n,m)d2t=1∑min(⌊dn⌋,⌊dm⌋)μ(t)⌊dtn⌋⌊dtm⌋令T=dtT=1∑min(n,m)⌊Tn⌋⌊Tm⌋d∣T∑d2μ(dT)令g(n)=d∣n∑d2μ(dn)原式=T=1∑min(n,m)⌊Tn⌋⌊Tm⌋g(T)
其中,对 T=dt 的换元思路来自于杜教筛。
于是,如果我们能预处理出来 g(n),那么每次询问使用数论分块就可以做到 O(N) 求解了。
不难发现 g=Id2∗μ,则 g 是一个积性函数,由于积性函数基本都可以使用线性筛求出,下面求递推式,类似LCMSUM - LCM Sum中的线性筛法:
-
g(1)=1
-
g(p)=μ(p)+p2μ(1)=p2−1
-
g(pk)=μ(pk)+p2μ(pk−1)+p4μ(pk−2)+⋯+p2(k−1)μ(p)+p2kμ(1)=0+⋯+0−p2(k−1)+p2k=p2k−2(p2−1)
令 i=a⋅pk,(gcd(a,p)=1,k⩾1),求 g(i⋅p) 的递推式。
⇒g(i⋅p)=g(a)g(pk+1)=g(a)p2k(p2−1)g(i)=g(a)g(pk)=g(a)p2k−2(p2−1)g(i⋅p)=p2⋅g(i)
于是就可以使用线性筛进行递推了。
总复杂度 O(Tn)。
点击显/隐代码
#include <bits/stdc++.h>
#define db double
#define ll long long
#define int ll
#define vi vector<int>
#define vii vector<vi >
#define pii pair<int, int>
#define vp vector<pii >
#define vip vector<vp >
#define mkp make_pair
#define pb push_back
#define Case(x) cout << "Case #" << x << ": "
using namespace std;
const int INF = 0x3f3f3f3f;
const int P = 1e9 + 7;
const int N = 2e7 + 10;
int g[N], sum[N];
bool vis[N];
vi prim;
void Euler(int n) {
g[1] = sum[1] = 1;
for (int i = 2; i <= n; i++) {
if (!vis[i]) {
prim.pb(i);
g[i] = (i * i - 1 + P) % P;
}
for (auto j : prim) {
int t = i * j;
if (t > n) break;
vis[t] = 1;
if (i % j == 0) {
g[t] = (j * j % P * g[i] % P);
break;
}
g[t] = g[i] * g[j] % P;
}
sum[i] = sum[i-1] + g[i];
}
}
int calc(int n, int m) {
int mn = min(n, m), ret = 0;
for (int l = 1, r; l <= mn; l = r + 1) {
r = min(n / (n / l), m / (m / l));
ret = (ret + (n / l) * (m / l) % P * (sum[r] - sum[l-1] + P) % P) % P;
}
return ret;
}
signed main(){
#ifdef _DEBUG
// FILE *file = freopen("out", "w", stdout);
#endif
ios::sync_with_stdio(0);
cin.tie(0);
Euler(2e7);
int T;
cin >> T;
while (T--) {
int n, m, p;
cin >> n >> m >> p;
cout << (p * calc(n, m) % P + m * calc(n, p) % P + n * calc(m, p) % P) % P << '\n';
}
return 0;
}