SDOI 2015 约数个数和

SDOI 2015 约数个数和

链接: https://www.luogu.com.cn/problem/P3327


\([x]\) 为逻辑判断函数,若 \(x\) 为真,则值为 \(1\),否则值为 \(0\)

一个重要的结论:

\[d(i\times j)=\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] \]

根据莫比乌斯反演有:

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

考虑将枚举因子改成枚举倍数:

\[\begin{aligned} &=\sum_{d}^{\min(n,m)}\mu(d)\sum_{i=1}^{\lfloor\frac{n} {d}\rfloor}\sum_{j=1}^{\lfloor\frac{m}{d}\rfloor}\sum_{x|i}\sum_{y|i}1 \\ &=\sum_{d}^{\min(n,m)}\mu(d)\sum_{x=1}^{\lfloor\frac{n}{d}\rfloor}\sum_{y=1}^{\lfloor\frac{m}{d}\rfloor}\lfloor\dfrac{\lfloor\frac{n}{d}\rfloor}{x}\rfloor\lfloor\dfrac{\lfloor\frac{m}{d}\rfloor}{y}\rfloor \\ &=\sum_{d}^{\min(n,m)}\mu(d)\times\sum_{x=1}^{\lfloor\frac{n}{d}\rfloor}\lfloor\dfrac{\lfloor\frac{n}{d}\rfloor}{x}\rfloor\times\sum_{y=1}^{\lfloor\frac{m}{d}\rfloor}\lfloor\dfrac{\lfloor\frac{m}{d}\rfloor}{y}\rfloor \end{aligned} \]

相当于是枚举 \(x,y\) 然后乘上它在枚举范围内的倍数个数,即它作出贡献的次数。而这个式子有 \(\lfloor\dfrac{n}{d}\rfloor,\lfloor\dfrac{m}{d}\rfloor\)。那么我们可以考虑用数论分块优化。

\(f(x)\) 为:

\[\sum_{i=1}^{x}\lfloor\dfrac{x}{i}\rfloor \]

这个式子可以通过枚举 \(i\)\(x\)\(O(n^2)\) 的时间内预处理,而可以用数论分块优化到 \(O(n\sqrt n)\)

那么答案就是:

\[\sum_d^{\min(n,m)}\mu(d)\times f(\lfloor\frac{n}{d}\rfloor)\times f(\lfloor\frac{m}{d}\rfloor) \]

这个式子在预处理后可以在 \(O(n)\) 的时间内求得,而利用数论分块则可以优化到 \(O(\sqrt n)\)

那么总时间复杂度就是 \(O(T\sqrt n+n\sqrt n)\)

代码如下:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN = 5e4+5;
const int MX = 5e4;
int n,m,prime[MAXN],pc,p[MAXN];
ll mu[MAXN],g[MAXN];
int main()
{
	mu[1]=1;
	for(int i=2;i<=MX;++i)
	{
		if(!p[i]) prime[++pc]=i,mu[i]=-1;
		for(int j=1;j<=pc&&1ll*i*prime[j]<=MX;++j)
		{
			p[i*prime[j]]=1;
			if(i%prime[j]==0)
			{
				mu[i*prime[j]]=0;
				break;
			}
			mu[i*prime[j]]=-mu[i];
		}
	}
	for(int i=1;i<=MX;++i) mu[i]+=mu[i-1];
	for(int i=1;i<=MX;++i)
	{
		ll ans=0;
		for(int l=1;l<=i;l=(i/(i/l))+1)
		{
			int r=i/(i/l);
			r=min(r,i);
			ans+=1ll*(r-l+1)*(i/l);
		}
		g[i]=ans;
	}
	int T;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d %d",&n,&m);
		int now=1,l1=1,l2=1,up=min(n,m);
		ll ans=0;
		while(now<=up)
		{
			int r=min(n/(n/l1),m/(m/l2));
			r=min(r,up);
			ans+=1ll*(mu[r]-mu[now-1])*g[n/l1]*g[m/l2];
			now=r+1;
			if(n/(n/l1)<=m/(m/l2)) l1=n/(n/l1)+1;
			else l2=m/(m/l2)+1;
		}
		printf("%lld\n",ans);
	}
	return 0;
}
posted @ 2022-01-04 16:44  夜空之星  阅读(22)  评论(0编辑  收藏  举报