[国家集训队]和与积
van了,不会反演了,之后复习了一下午反演终于会做了
这个东西一看就很神的样子
先分析一下,这个\({(i+j)}|i\times j\)有什么含义吧
首先这是一道数学题,那么就极有可能需要往\(gcd\)上靠,于是我们设\(d=(i,j),i=k_1d,j=k_2d\)
显然存在\(k_1\perp k_2,k_1<k_2\)
那么如果满足\({(i+j)}|i\times j\)的话,就会有\((k_1+k_2)d|k_1k_2d^2\),也就是\((k_1+k_2)|k_1k_2d\)
我们需要使用一个结论,对于\(k_1\perp k_2\),就会存在\((k_1+k_2,k_1k_2)=1\)
这个结论非常好证,首先根据更相减损,\((k_1+k_2,k_1)=(k_1+k_2,k_2)=1\),既然\(k_1+k_2\)和\(k_1\)以及\(k_2\)都互质,那么显然也和\(k_1k_2\)互质
于是我们现在就把条件写成了\((k_1+k_2)|d\)
这样有什么用呢,考虑\(i\)和\(j\)都增加\(k\)倍,这个时候\(k_1,k_2\)不变,\(d\)变成了\(kd\),这个时候\((k_1+k_2)|kd\)仍然是成立的
这启示我们枚举\(k_1,k_2\),之后让\(d=k_1+k_2\),这样我们就可以一直让\(d\)加倍来统计答案了
于是我们得到了这样一个柿子
后面那个奇奇怪怪的东西是这样的,\(i+j\)就是\(d\),这样\(jd\)就是一个数对中较大的那个数,这个\(\left \lfloor \frac{n}{j(i+j)} \right \rfloor\)就表示\(jd\)的倍数在\(n\)中出现的次数
对于这个柿子我们直接把\(i\perp j\)写成\(\sum_{d|i,d|j}\mu(d)\)就好了
稍微交换一下求和符号,就能写成
我们注意到为了让后面的\(\left \lfloor \frac{n}{id^2} \right \rfloor>0\),\(d\leq \sqrt{n},i\leq \left \lfloor \frac{\sqrt{n}}{d} \right \rfloor\),这样我们暴力枚举\(d,i\),后面整除分块就好了
代码
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#define re register
#define min std::min
#define LL long long
const int maxn=5e4+5;
int n,m;LL ans;
int mu[maxn],f[maxn],p[maxn>>1];
inline LL calc(int n,int L,int R) {
LL cnt=0;
for(int l=L,r; l<=R; l=r+1) {
if(!(n/l)) break;
r=n/(n/l);r=min(r,R);
cnt+=(n/l)*(r-l+1);
}
return cnt;
}
int main() {
scanf("%d",&n);
m=ceil(std::sqrt(n));f[1]=mu[1]=1;
for(re int i=2; i<=m; i++) {
if(!f[i]) p[++p[0]]=i,mu[i]=-1;
for(re int j=1; j<=p[0]&&i*p[j]<=m; j++) {
f[p[j]*i]=1;if(i%p[j]==0) break;mu[p[j]*i]=-mu[i];
}
}
for(re int d=1; d<=m; d++) {
if(!mu[d]) continue;
for(re int i=1; i*d<=m; i++) {
int t=n/i/d/d;
if(!t) continue;
ans+=mu[d]*calc(t,i+1,2*i-1);
}
}
printf("%lld\n",ans);
return 0;
}