http://www.lydsy.com/JudgeOnline/problem.php?id=3994 (题目链接)
题意
多组询问,给出${n,m}$,求${sum_{i=1}^nsum_{j=1}^m d(i×j)}$,${d(i×j)}$为${ij}$的约数个数。
Solution
看到这个式子感觉无从下手,这个${d(ij)}$比较丑,有一个比较经典的公式:$${d(nm)=sum_{i|n}sum_{j|m} [gcd(i,j)=1]}$$
这个是怎么得来的呢。每一个${nm}$的约数都可以表示成这样的形式:${i×frac{m}{j}}$。其中${i}$是${n}$的约数,${j}$是${m}$的约数。
那么如果我们直接枚举${i,j}$来统计的话,可能会有重复,所以就要加上一个条件:${[gcd(i,j)=1]}$,也就是${i,j}$互质。那他们不互质为什么就不行呢,假设${i,j}$都含有一个约数${p}$,那么${frac{m}{j}}$就表示从${m}$中拿掉${j}$用剩下的去组成约数,也就是从约数中拿掉了${p}$;而${i}$就是从${n}$中拿${i}$去组成约数,也就向约数中加入了${p}$。那么我们拿掉一个${p}$又加入一个${p}$,不是吃多了吗→_→,这样自然会算重复啦。
解决了这个问题,我们就很好做了。开始推式子。
egin{aligned} sum_{i=1}^{n}sum_{j=1}^{m}d(ij) =& sum_{i=1}^nsum_{j=1}^msum_{u|i}sum_{v|j}[gcd(u,v)=1] \ =&sum_{u=1}^nsum_{v=1}^m[gcd(u,v)=1]lfloorfrac{n}{i} floorlfloorfrac{m}{j} floor \ =&sum_{t=1}^nμ(t)sum_{i=1}^{lfloor{n/t} floor}sum_{j=1}^{lfloor{m/t} floor}lfloorfrac{n}{ti} floorlfloorfrac{m}{tj} floor \ =&sum_{t=1}^nμ(t)sum_{i=1}^{lfloor{n/t} floor}lfloorfrac{lfloor{n/t} floor}{i} floorsum_{j=1}^{lfloor{m/t} floor}lfloorfrac{lfloor{m/t} floor}{j} floor end{aligned}
我们令${f(n)=sum_{i=1}^nlfloor{n/i} floor}$,那么${f}$是可以分段${O(nsqrt{n})}$的预处理出来的。$${原式=sum_{t=1}^{n}μ(t)f(lfloor{n/t} floor)f(lfloor{m/t} floor)}$$
这样我们就可以预处理出${μ}$的前缀和,然后分段求解答案就可以了。
细节
LL,预处理不要太多会TLE。
代码
// bzoj3994
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cstdio>
#include<vector>
#include<cmath>
#define LL long long
#define inf 2147483640
#define Pi acos(-1.0)
#define free(a) freopen(a".in","r",stdin),freopen(a".out","w",stdout);
using namespace std;
const int maxn=50010;
LL n,m,s[maxn],mu[maxn],f[maxn];
int p[maxn],vis[maxn];
int main() {
int T;scanf("%d",&T);
s[1]=mu[1]=1;
for (int i=2;i<maxn;i++) {
if (!vis[i]) p[++p[0]]=i,mu[i]=-1;
for (int j=1;j<=p[0] && p[j]*i<maxn;j++) {
vis[i*p[j]]=1;
if (i%p[j]==0) {mu[i*p[j]]=0;break;}
else mu[i*p[j]]=-mu[i];
}
s[i]=s[i-1]+mu[i];
}
for (int i=1;i<maxn;i++)
for (int j=1,k;j<=i;j=k+1) {
k=i/(i/j);
f[i]+=(LL)(k-j+1)*(i/j);
}
while (T--) {
scanf("%lld%lld",&n,&m);
if (n>m) swap(n,m);
LL ans=0;
for (int i=1,j;i<=n;i=j+1) {
j=min(n/(n/i),m/(m/i));
ans+=f[n/i]*f[m/i]*(s[j]-s[i-1]);
}
printf("%lld
",ans);
}
return 0;
}