洛谷3327:约数个数和
题意描述:
- 设(d(x))为(x)的约数个数,给定(n,m)求:
- (sum_{i=1}^nsum_{j=1}^md(ij))。(T)组数据。
- 数据范围(T,n,mleq 50000).
思路
- (d(ij)=sum_{x|i}sum_{y|j}[gcd(x,y)=1]).
- 所求就为:
- (sum_{i=1}^nsum_{j=1}^msum_{x|i}sum_{y|j}[gcd(x,y)=1])。
- 枚举(x,y)有:
- (sum_{x=1}^nsum_{y=1}^m[gcd(x,y)=1]sum_{i=1}^nsum_{j=1}^m[x|n and y|m]).
- (sum_{x=1}^nsum_{y=1}^m[gcd(x,y)=1]frac{n}{x}frac{m}{y})
- 修改一下变量吧...:
- (sum_{i=1}^nsum_{j=1}^m[gcd(i,j)=1]frac{n}{i}frac{m}{j}).
- 开始反演:
- (f(x)=sum_{i=1}^nsum_{j=1}^m[gcd(i,j)=x]frac{n}{i}frac{m}{j}).
- 按照套路有:
- (F(x)=sum_{x|d}f(d)=sum_{i=1}^nsum_{j=1}^mfrac{n}{i}frac{m}{j}[x|gcd(i,j)])。
- 枚举(ix,jx),有
- (=sum_{i=1}^{frac{n}{x}}sum_{j=1}^{frac{m}{x}}frac{n}{ix}frac{m}{jx}).
- 这样就可以不用理会(gcd(i,j))这个条件。
- 有反演公式:
- (f(x)=sum_{x|d}mu(frac{d}{x})F(d)).
- 又知道(ans=f(1))。
- (=sum_{1|d}mu(d)F(d))
- (=sum_{d=1}^{min(n,m)}mu(d)sum_{i=1}^{n/d}frac{n}{di}sum_{j=1}^{m/d}frac{m}{dj}).
- 到了这里其实就可以做了,但是还有个小问题,就是如何快速的计算(sum_{i=1}^nfrac{n}{i})。
- 可以用整除分块来处理。
#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef long long ll;
const int maxn = 5e4 + 10;
int T, n, m;
bool vis[maxn];
int primes[maxn], mu[maxn], cnt, sum[maxn];
ll s[maxn];
void get_mu(int n)
{
mu[1] = 1;
for(int i = 2; i <= n; i++)
{
if(!vis[i])
{
primes[++cnt] = i;
mu[i] = -1;
}
for(int j = 1; primes[j] <= n/i; j++)
{
vis[primes[j]*i] = 1;
if(i % primes[j] == 0) break;
else mu[i*primes[j]] = -mu[i];
}
}
for(int i = 1; i <= n; i++)
sum[i] = sum[i-1] + mu[i];
for(int i = 1; i <= n; i++)
{
ll res = 0;
for(int l = 1, r; l <= i; l = r + 1)
{
r = i/(i/l);
res += 1ll*(r-l+1)*1ll*(i/l);
}
s[i] = res;
}
}
inline void solve(int n, int m)
{
if(n > m) swap(n, m);
ll ans = 0;
for(int l = 1, r; l <= n; l = r+1)
{
r = min(n/(n/l), m/(m/l));
ans += 1ll*(sum[r]-sum[l-1])*1ll*s[n/l]*1ll*s[m/l];
}
printf("%lld
", ans);
}
signed main()
{
get_mu(50000);
scanf("%lld", &T);
while(T--)
{
scanf("%lld%lld", &n, &m);
solve(n, m);
}
return 0;
}