题目:
题意:
定义 f(x) 表示 x 的素因子的个数 (包括相同的),如果 f(x) <= p,则称 x 是 p 的幸运数;Q 次询问,每次给出 N,M,P,求有多少对 (a,b) 使得 gcd(a,b) 是 p 的幸运数,并且 1=< a <= N,1 =< b <= M
分析:
题目所求等价于(假设 n <= m):
最后是令 T = gd 化简而来;发现后面一堆可以 O(1) 直接计算,又是向下取整,只要知道 S(x) 函数的前缀和就可以分块加速计算;首先,p >= 20 时,那么最后答案一定是 N*M (因为 5e5 范围最多 20 个素因子);考虑如何计算 p < 20 时,S(x) 的前缀和:
设 G[x][k] 表示 p == k 时,S(x) 的值;设 sum[x][k] 表示 p == k 时,S(x) 的前缀和
g[x][p] 表示下式的值:
那么我们可以预处理出 g[x][p],再对 g[x][p] 求一遍前缀和,得到 G[x][p]; 再对 G[x][p] 求一遍前缀和即可得到 sum[x][p]
考虑如何预处理 g[x][p],先把 5e5 范围内的素数筛出来,然后用这些素数去分解 x 得到 f(x)【这样比暴力分解要快】,然后用埃式筛的思想得到每个数因子即可求出 g[x][p]
预处理的复杂度为 O(NlogN),每次询问的复杂度为 O(根号N)
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn = 5e5+15;
int prime[maxn],mul[maxn],cnt,vis[maxn],f[maxn];
LL sum[maxn][20],g[maxn][20];
int cal(int x){
int num = 0;
for(int i = 0; i<cnt&&1ll*prime[i]*prime[i]<=x; ++i){
while(x%prime[i] == 0){
num++; x /= prime[i];
}
}
return x > 1 ? num+1 : num;
}
void init(){
mul[1] = 1;
for(int i = 2;i < maxn; ++i){
if(!vis[i]){
mul[i] = -1; prime[cnt++] = i;
}
for(int j=0;j<cnt&&1ll*i*prime[j]<maxn; ++j){
int x = i*prime[j]; vis[x] = 1;
if(i%prime[j] == 0){ mul[x] = 0; break; }
else mul[x] = -mul[i];
}
}
for(int i = 2;i < maxn; ++i) f[i] = cal(i); // 计算f[x]
for(int i = 1;i < maxn; ++i){ // 计算g[x][p]
for(int j = i;j < maxn; j+=i){
g[j][f[i]] += mul[j/i];
}
}
for(int i = 1;i < maxn; ++i){ // 计算sum[x][p]
sum[i][0] = sum[i-1][0]+g[i][0];
for(int j = 1;j < 20; ++j){
g[i][j] += g[i][j-1];
sum[i][j] = sum[i-1][j] + g[i][j];
}
}
}
LL solve(int n,int m,int p){ // 分块加速计算
if(p >= 20) return 1ll*n*m;
if(n > m) swap(n,m);
LL res = 0;
for(int i = 1,last;i <= n;i = last+1){
last = min(n/(n/i),m/(m/i));
res += (sum[last][p]-sum[i-1][p])*(n/i)*(m/i);
}
return res;
}
int main(){
init();
int n,m,p,q; scanf("%d",&q);
while(q--){
scanf("%d %d %d",&n,&m,&p);
printf("%I64d\n",solve(n,m,p));
}
return 0;
}