luogu P3327 [SDOI2015]约数个数和

第一道自己推出来的莫比乌斯反演题。

下文默认 \(n\le m\)

下文 \(\varepsilon(x)=[x=1]=\sum_{d|x}\mu(d)\)


题意:求

\[\sum_{x=1}^{n}\sum_{y=1}^{m}d(xy) \]

可以在度娘上找到

\[d(xy)=\sum_{i=1}^{n}[i|x]\sum_{j=1}^{m}[j|y][\gcd(i,j)=1] \]

带入式子

\[= \sum_{x=1}^n\sum_{y=1}^m\sum_{i=1}^{n}[i|x]\sum_{j=1}^{m}[j|y][\gcd(i,j)=1] \]

更换枚举顺序,先枚举 \(i,j\),则前面枚举的 \(x,y\) 需满足 \(i|x,j|y\)

\[= \sum_{i=1}^{n}\sum_{j=1}^{m}\sum^n_{i|x}\sum^m_{j|y}[\gcd(i,j)=1] \]

可以发现 \(\sum^n_{i|x}\sum^m_{j|y}\),这两个式子就是分别求 \(1\) ~ \(n\)\(i\) 的倍数的个数和 $1 $ ~ \(m\)\(j\) 的倍数的个数,这两个和式等价于 \(n/i,m/j\),所以

\[= \sum_{i=1}^{n}\sum_{j=1}^{m} \left \lfloor \frac{n}{i}\right \rfloor \left \lfloor \frac{m}{j}\right \rfloor [\gcd(i,j)=1] \]

套路的把 \([\gcd(i,j)=1]\) 换成 \(\varepsilon(\gcd(i,j))\),再把它转成 \(\sum_{d|\gcd(i,j)}\mu(d)\) 的形式

\[= \sum_{i=1}^{n}\sum_{j=1}^{m} \left \lfloor \frac{n}{i}\right \rfloor \left \lfloor \frac{m}{j}\right \rfloor \sum_{d|\gcd(i,j)}\mu(d) \]

改变枚举顺序,先枚举 \(d\),并把 \(\mu(d)\) 提到式子前面,因为 \(d|\gcd(i,j)\),必有 \(d|i ,d|j\)

\[= \sum_{d=1}^{n}\mu(d)\sum_{i=1}^{n}\sum_{j=1}^{m} [d|i,d|j]\left \lfloor \frac{n}{i}\right \rfloor \left \lfloor \frac{m}{j}\right \rfloor \]

把后面两个和式改成枚举 \(di,dj\) 的形式,则上标都除以 \(d\)\(di,dj\) 此时都是 \(d\) 的倍数,后面含有 \(i,j\) 的向下取整的都要变成 \(di,dj\)

\[= \sum_{d=1}^{n}\mu(d)\sum_{i=1}^{\left \lfloor \frac{n}{d}\right \rfloor}\sum^{\left \lfloor \frac{m}{d}\right \rfloor}_{j=1}\left \lfloor \frac{n}{di}\right \rfloor\left \lfloor \frac{m}{dj}\right \rfloor \]

把其中一个向下取整移出来

\[= \sum_{d=1}^{n}\mu(d)\sum_{i=1}^{\left \lfloor \frac{n}{d}\right \rfloor}\left \lfloor \frac{n}{di}\right \rfloor\sum^{\left \lfloor \frac{m}{d}\right \rfloor}_{j=1}\left \lfloor \frac{m}{dj}\right \rfloor \]

这下两边都是数论分块可以求出的了。

预处理出 \(g(n)=\sum_{i=1}^{n} \left\lfloor \frac{n}{i}\right \rfloor\),则两个和式变成 \(g(\left \lfloor \frac{n}{d}\right \rfloor),g(\left \lfloor \frac{m}{d}\right \rfloor)\),所以

\[ans=\sum_{d=1}^{n} \mu(d)g(\left \lfloor \frac{n}{d}\right \rfloor)g(\left \lfloor \frac{m}{d}\right \rfloor) \]

这个式子也用数论分块做即可,后面因为预处理了,可以 \(O(1)\) 做。

总复杂度 \(O(T\sqrt n)\)

\(Code\)

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
#define int long long 
inline int read(){
	register int x=0,f=0,ch=getchar();
	while('0'>ch||ch>'9')f^=ch=='-',ch=getchar();
	while('0'<=ch&&ch<='9')x=(x<<1)+(x<<3)+(ch^'0'),ch=getchar();
	return f?-x:x;
}
const int MAX=50005; 
int p[MAX],tot=0,f[MAX],mu[MAX];
inline void init(){
	mu[1]=1;
	for(register int i=2;i<=MAX;++i){
		if(!f[i])p[++tot]=i,mu[i]=-1;
		for(register int j=1;j<=tot&&i*p[j]<=MAX;++j){
			f[i*p[j]]=1;
			if(i%p[j]==0)break;
			mu[i*p[j]]=-mu[i];
		}
	}
	memset(f,0,sizeof(f));
	for(register int i=1;i<=MAX;++i){
		mu[i]+=mu[i-1];
		for(register int l=1,r;l<=i;l=r+1){
			r=i/(i/l);
			f[i]+=1LL*(r-l+1)*(i/l);
		}
	}
}
int ans,T;
inline int solve(int n,int m){
	ans=0;
	for(register int l=1,r;l<=n;l=r+1){
		r=min(n/(n/l),m/(m/l));
		ans+=1LL*(mu[r]-mu[l-1])*f[n/l]*f[m/l];
	}
	return ans;
}
int n,m;
signed main(){
	init();
	T=read();
	while(T--){
		n=read(),m=read();	
		if(n>m)n^=m^=n^=m;
		printf("%lld\n",solve(n,m));
	}
	return 0;
}


posted @ 2020-04-13 22:54  Lates  阅读(184)  评论(1编辑  收藏  举报