洛谷3327:约数个数和
题意描述:
- 设\(d(x)\)为\(x\)的约数个数,给定\(n,m\)求:
- \(\sum_{i=1}^n\sum_{j=1}^md(ij)\)。\(T\)组数据。
- 数据范围\(T,n,m\leq 50000\).
思路
- \(d(ij)=\sum_{x|i}\sum_{y|j}[gcd(x,y)=1]\).
- 所求就为:
- \(\sum_{i=1}^n\sum_{j=1}^m\sum_{x|i}\sum_{y|j}[gcd(x,y)=1]\)。
- 枚举\(x,y\)有:
- \(\sum_{x=1}^n\sum_{y=1}^m[gcd(x,y)=1]\sum_{i=1}^n\sum_{j=1}^m[x|n\ and\ y|m]\).
- \(\sum_{x=1}^n\sum_{y=1}^m[gcd(x,y)=1]\frac{n}{x}\frac{m}{y}\)
- 修改一下变量吧...:
- \(\sum_{i=1}^n\sum_{j=1}^m[gcd(i,j)=1]\frac{n}{i}\frac{m}{j}\).
- 开始反演:
- \(f(x)=\sum_{i=1}^n\sum_{j=1}^m[gcd(i,j)=x]\frac{n}{i}\frac{m}{j}\).
- 按照套路有:
- \(F(x)=\sum_{x|d}f(d)=\sum_{i=1}^n\sum_{j=1}^m\frac{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}^n\frac{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\n", ans);
}
signed main()
{
get_mu(50000);
scanf("%lld", &T);
while(T--)
{
scanf("%lld%lld", &n, &m);
solve(n, m);
}
return 0;
}