设 d(x) d ( x ) 为 x x 的约数个数,给定 N N 、 M M ,求 ∑Ni=1∑Mj=1d(ij) ∑ i = 1 N ∑ j = 1 M d ( i j )
输入格式:
输入文件包含多组测试数据。第一行,一个整数T,表示测试数据的组数。接下来的T行,每行两个整数N、M。
输出格式:
T行,每行一个整数,表示你所求的答案。
输入样例#1:
2
7 4
5 6
输出样例#1:
110
121
1<=N, M<=50000
1<=T<=50000
数论题对我这种智障来说就是要命的。这题我想了一会还是没有想出来。
某Cla口中的水题对蒟蒻我来说都是难题,所谓的好题就是我根本不会做的题。。。
本题必须用到的结论:
证明很简单:
将一个数唯一分解为 pk11pk22pk33...pknn p 1 k 1 p 2 k 2 p 3 k 3 . . . p n k n ,则其约数个数为 (k1+1)(k2+1)(k3+1)...(kn+1) ( k 1 + 1 ) ( k 2 + 1 ) ( k 3 + 1 ) . . . ( k n + 1 ) 。因为对于任意 i≠j i ≠ j , (pi,pj)=1 ( p i , p j ) = 1 。
然后考虑一个质数 p p 对 d(ij) d ( i j ) 的贡献。假设 i i 的质因数分解中有 k k 个 p p , j j 的质因数分解中有 q q 个 p p ,那么对 d(ij) d ( i j ) 产生的贡献就是 k+q+1 k + q + 1 。接下来这条关键的式子又不容易想到(反正我想不到):
按照某Cla的说法,这就相当于一个 (k+1)∗(q+1) ( k + 1 ) ∗ ( q + 1 ) 的矩形,只取了左下角的”L”字型的一块。 (然而我还不知道这有什么用)
然后根据前面的规律,我们设 i=pk11pk22pk33...pknn i = p 1 k 1 p 2 k 2 p 3 k 3 . . . p n k n , j=pq11pq22pq33...pqnn j = p 1 q 1 p 2 q 2 p 3 q 3 . . . p n q n ,逐一考虑每个质数 pi p i ,根据乘法原理,有
我们令 x=px11px22px33...pxnn x = p 1 x 1 p 2 x 2 p 3 x 3 . . . p n x n , y=py11py22py33...pynn y = p 1 y 1 p 2 y 2 p 3 y 3 . . . p n y n ,就得到了一开始的结论
其实很好理解, x x 就代表了 pxii p i x i 的总出现状态,分开的式子相当于分组计数再乘法合并,合起来相当于直接枚举总状态计数,本质就是在统计数对的数量(显然所有质因数不同时出现等价于原数互质)。
现在就变成求
根据数论中切换枚举次序的套路以及莫比乌斯反演对布尔条件框的替换,我们得到
原式
将 gcd g c d 用枚举去掉
最后将约数 k k 的枚举提到外面,内部枚举 k k 的互补约数
然后我们设 g(n)=∑ni=1⌊ni⌋ g ( n ) = ∑ i = 1 n ⌊ n i ⌋ ,那么 μ(k) μ ( k ) 右边的部分变成 g(⌊nk⌋)g(⌊mk⌋) g ( ⌊ n k ⌋ ) g ( ⌊ m k ⌋ ) 。
这样对于连续一段 k k , g g 函数的参数相同,函数值也相同,于是预处理 μ μ 的前缀和,将第一个枚举改成下底函数分块。
g g 函数也用下底函数分块来去处理,算出所有 g g 的时间是 O(nn−−√) O ( n n ) 。
还有一种做法,就是 g g 函数其实是约数个数的前缀和( g(n)=∑ni=1∑d|i1=∑nd=1⌊nd⌋ g ( n ) = ∑ i = 1 n ∑ d | i 1 = ∑ d = 1 n ⌊ n d ⌋ )。做线性筛的同时能顺带求出约数个数,最后做一次前缀和就行了。(某Cla想到的)
#include
#include
#include
#include
#include
#include
#define N 50010
using namespace std;
typedef long long LL;
int T, n, m;
LL g[N], Ans;
int prime[N], miu[N], cnt;
bool vis[N];
void Get_miu(){
for(int i = 1; i < N; i++){
int last;
for(int j = 1; j <= i; j = last+1){
last = i/(i/j);
g[i] += (LL)(i/j)*(LL)(last-j+1);
}
}
vis[1] = true;
miu[1] = 1;
for(int i = 2; i < N; i++){
if(!vis[i]){
prime[++cnt] = i;
miu[i] = -1;
}
for(int j = 1; j <= cnt && i * prime[j] < N; j++){
vis[i * prime[j]] = true;
if(i % prime[j] == 0){
miu[i * prime[j]] = 0;
break;
}
else miu[i * prime[j]] = -miu[i];
}
}
for(int i = 1; i < N; i++) miu[i] += miu[i-1];
}
int main(){
Get_miu();
scanf("%d", &T);
while(T --){
scanf("%d%d", &n, &m);
if(n > m) swap(n, m);
Ans = 0;
int last;
for(int i = 1; i <= n; i = last+1){
last = min(n/(n/i), m/(m/i));
Ans += (LL)(miu[last] - miu[i-1]) * g[n/i] * g[m/i];
}
printf("%lld\n", Ans);
}
return 0;
}
给所有的现充施以破坏的铁锤吧!