Problem
【SDOI2015】约数个数和
TimeLimit:20Sec
MemoryLimit:128MB
Description
设d(x)为x的约数个数,给定N,M,求∑i=1n∑j=1md(i⋅j)
输入文件包含多组测试数据。
第一行,一个整数T,表示测试数据的组数。
接下来的T行,每行两个整数N,M。
Output
T行,每行一个整数,表示你所求的答案。
Sample Output
HINT
1≤N,M≤50000,1≤T≤50000
标签:莫比乌斯反演
Solution
挺神的反演,没推出来,需要一个结论。
首先考虑如何把d(x)变为我们更为熟悉的数学语言。
对于d(x⋅y),考虑x×y的约数,每个约数均可表示为i×jy,其中i∣x,j∣y。那么用∑i∣x∑j∣y1统计约数,一定会不漏地枚举到所有约数,但显然是有重复的。注意到这种重复的造成只有一种情况,即若(i,j)符合条件,那么(i×t,j×t)也符合条件,而两者所代表的最终约数是相同的,重复计数。也就是说只要gcd(i,j)=1,那么一定是重复计算的。于是不重不漏地计算只需要把∑i∣x∑j∣y1中的1加上gcd(i,j)=1的限制即可。
因此推出重要结论d(x⋅y)=∑i∣x∑j∣y[gcd(i,j)=1]。
接下来就可以推反演了:
Ans=k=1∑nw=1∑md(k⋅w)=k=1∑nw=1∑mi∣k∑j∣w∑[gcd(i,j)=1]=i=1∑nj=1∑m⌊in⌋⌊jm⌋[gcd(i,j)=1]=i=1∑nj=1∑m⌊in⌋⌊jm⌋d∣i,d∣j∑μ(d)=d=1∑nμ(d)i=1∑⌊n/d⌋j=1∑⌊m/d⌋⌊i⋅dn⌋⌊j⋅dm⌋=d=1∑nμ(d)i=1∑⌊n/d⌋⌊i⋅dn⌋j=1∑⌊m/d⌋⌊j⋅dm⌋
如果能预处理出F(x)=∑i=1x⌊ix⌋的值,就可以根号分块计算答案。
考虑∑i=1x⌊ix⌋的意义,即枚举一个数,统计其在[1,x]内的倍数有多少个,可以理解为枚举约数,计算它在1∼x中是多少个数的约数,即计算其对∑i=1xd(i)的贡献。于是F(x)=∑i=1xd(i),我们需要预处理出d(x)。
由于d(x)是积性函数,对于d(x⋅p)(x∈N∗,p<x,p是质数),我们有:
- p∤x:d(x⋅p)=d(x)⋅d(p)
- p∣x:d(x⋅p)=d(pkx⋅pk+1)=d(pkx)⋅d(pk+1)=k+1d(x)⋅(k+2),其中p一定为x的最小质因子
为了应对情况2,我们需要预处理最小质因子次数c(x),注意到c(x)也可以线性筛预处理:
- 对于质数p,c(p)=1
- 对于正整数x和质数p,
- p∤x:c(x⋅p)=1
- p∣x:c(x⋅p)=c(x)+1
如此我们即可线性筛预处理出c(x),d(x),计算d(x)前缀和F(x),对于询问进行数论分块统计答案,时间复杂度O(Tn)。
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| #include <bits/stdc++.h> #define MAX_N 50000 using namespace std; typedef long long lnt; template <class T> inline void read(T &x) { x = 0; int c = getchar(), f = 1; for (; !isdigit(c); c = getchar()) if (c == 45) f = -1; for (; isdigit(c); c = getchar()) (x *= 10) += f*(c-'0'); } bool NotPri[MAX_N+5]; int pri[MAX_N+5], cnt; lnt mu[MAX_N+5], c[MAX_N+5], d[MAX_N+5], ans; void init() { mu[1] = 1, d[1] = 1; for (int i = 2; i <= MAX_N; i++) { if (!NotPri[i]) pri[cnt++] = i, mu[i] = -1, c[i] = 1, d[i] = 2; for (int j = 0; j < cnt; j++) { if (i*pri[j] > MAX_N) break; NotPri[i*pri[j]] = true; if (i%pri[j]) mu[i*pri[j]] = -mu[i], c[i*pri[j]] = 1, d[i*pri[j]] = d[i]*d[pri[j]]; else mu[i*pri[j]] = 0, c[i*pri[j]] = c[i]+1, d[i*pri[j]] = d[i]*(c[i]+2)/(c[i]+1); if (i%pri[j] == 0) break; } } for (int i = 2; i <= MAX_N; i++) mu[i] += mu[i-1], d[i] += d[i-1]; } int main() { int n, m, T; read(T), init(); while (T--) { read(n), read(m), ans = 0; for (int l = 1, r; l <= min(n, m); l = r+1) r = min(n/(n/l), m/(m/l)), ans += (mu[r]-mu[l-1])*d[n/l]*d[m/l]; printf("%lld\n", ans); } return 0; }
|