jzoj4496-[GDSOI2016]互补约数【莫比乌斯反演】

正题

题目链接:https://gmoj.net/senior/#main/show/4496


题目大意

给出\(n\),定义

\[f(i)=\sum_{d|i}gcd(d,\frac{i}{d}) \]

\[\sum_{i=1}^nf(i) \]

\(1\leq n\leq 10^{11}\)


解题思路

考虑枚举\(x=d\)\(y=\frac{n}{d}\)

\[\sum_{x=1}\sum_{y=1}[xy\leq n]gcd(x,y) \]

\[\sum_{d=1}d\sum_{x=1}\sum_{y=1}[xyd^2\leq n][gcd(x,y)=1] \]

然后莫反

\[\sum_{d=1}d\sum_{k=1}\mu(k)\sum_{x=1}\sum_{y=1}[xyd^2k^2\leq n] \]

\[\sum_{d=1}d\sum_{k=1}\mu(k)\sum_{x=1}\lfloor\frac{\lfloor\frac{n}{d^2k^2}\rfloor}{x}\rfloor \]

因为要求\(k^2d^2\leq n\)所以可以考虑暴力枚举\(k\)\(d\),然后最后那个整除分块就好了。

这样会慢几秒,把后面那个式子每次算的时候顺便记忆化了就可以了。


code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<map>
#define ll long long
using namespace std;
const ll N=316228;
ll n,ans,mu[N],pri[N/10],p1[N],p2[N],cnt,S,T;
bool v[N];map<ll,ll> mp;
void Prime(){
	mu[1]=1;
	for(ll i=2;i<N;i++){
		if(!v[i])pri[++cnt]=i,mu[i]=-1;
		for(ll j=1;j<=cnt&&i*pri[j]<N;j++){
			v[i*pri[j]]=1;
			if(i%pri[j]==0)break;
			mu[i*pri[j]]=-mu[i];
		}
	}
}
ll GetF(ll n){
	ll ans=0;
	if(n>=T&&p2[S/n])return p2[S/n];
	if(n<T&&p1[n])return p1[n];
	for(ll l=1,r;l<=n;l=r+1){
		r=n/(n/l);
		ans+=(n/l)*(r-l+1); 
	}
	if(n>=T)return p2[S/n]=ans;
	return p1[n]=ans;
}
ll GetG(ll n){
	ll ans=0;
	for(ll i=1;i*i<=n;i++)
		ans+=mu[i]*GetF(n/i/i);
	return ans;
}
signed main()
{
	freopen("gcd.in","r",stdin);
	freopen("gcd.out","w",stdout);
	Prime();
	scanf("%lld",&n);
	T=sqrt(n);S=n;
	for(ll i=1;i*i<=n;i++)
		ans+=i*GetG(n/i/i);
	printf("%lld\n",ans);
	return 0;
}
posted @ 2021-07-13 14:14  QuantAsk  阅读(40)  评论(0编辑  收藏  举报