[SDOI2015]约数个数和

[SDOI2015]约数个数和

闲话:莫比乌斯反演经典题

题目大意

\[\sum_{i=1}^n \sum_{j=1}^m \sigma_0 (ij) \]

\(T\) 组数据,且 \(1 \leq T,n,m \leq 50000\)
良心样例

输入样例

2
7 4
5 6

输出样例

110
121

一条有用的结论

\[\sigma_0 (ij) = \sum_{x|i} \sum_{y|j} [\gcd(x,y)=1] \]

小小的证明
对于一个 \(d|ij\) ,我们构造 \(x=\frac{i}{\gcd(i,d)} , y=\frac{d}{\gcd(i,d)}\)
现在我们就要找到 \(x,y\) 之间的关系,使我们能通过 \(x,y\) 得到 \(d\),而且能与所有的 \(d\) 一一对应
显然得到 \(\gcd(x,y)=1 , x|i\)
\(y \gcd(i,j) | ij\)
\(y | xj\)
又因为 \(\gcd(x,y)=1\)
所以 \(y|j\)
把所得的结论综合起来
\(\gcd(x,y)=1 , x|i , y|j\)
此时对于满足 \(\gcd(x,y)=1 , x|i , y|j\)\(x,y\) 都可以得到 \(\frac{iy}{x}|ij\)
并且与所有的 \(d|ij\) 以一对应
也就是说用满足上述条件的 \(x,y\) 来构造 \(d\) ,能与所有的 \(d\) 一一对应
故这条有用的结论得证

正式切题

然后开始推式子
此时可以从两个方面入手,利用

\[\sum_{d|n} \mu(d) = [n=1] \]

\[F(x)=\sum_{x|d}f(d) \Longleftrightarrow f(x)=\sum_{x|d}\mu(\frac{d}{x})F(d) \]

化简式子
但无论如何,化简后的式子是一样的
所谓“殊途同归”

第一种推法

\[\begin{aligned} \sum_{i=1}^n \sum_{j=1}^m \sigma_0 (ij) &= \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) \\ &= \sum_{d=1}^{\min(n,m)} \mu(d) \sum_{d|x} \sum_{d|y} \sum_{x|i} \sum_{y|j}1 \\ &= \sum_{d=1}^{\min(n,m)} \mu(d) \sum_{d|x} \sum_{d|y} \lfloor \frac{n}{x} \rfloor \lfloor \frac{m}{y} \rfloor \\ &= \sum_{d=1}^{\min(n,m)} \mu(d) \sum_{x=1}^{\lfloor \frac{n}{d} \rfloor} \sum_{y=1}^{\lfloor \frac{m}{d} \rfloor} \lfloor \frac{n}{dx} \rfloor \lfloor \frac{m}{dy} \rfloor \\ &= \sum_{d=1}^{\min(n,m)} \mu(d) f(\lfloor \frac{n}{d} \rfloor) f(\lfloor \frac{m}{d} \rfloor) \end{aligned} \]

似乎已经有人注意到了
忽然出现了个 \(f\)
其实不必惊讶
我们发现后面那堆是因为这两个式子只和 \(\lfloor \frac nd \rfloor,\lfloor \frac md \rfloor\) 有关
故我们可以令 \(f(n)=\sum_{i=1}^n \lfloor \frac ni \rfloor\)
然后套用
发现一个问题
我们如何处理 \(f\)
你可以选择用到的时候再算 \(f\)
也可以 \(O(n \sqrt n)\) 预处理
如果选择前者就要注意了
计算过了的 \(f\) 就要记录下来,否则会超时
事实上
我的两种算 \(f\) 的方式在洛谷评测机前者跑得更快
尤其是在总时间上,快了差不多 \(2s\)

第二种推法
如旧有

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

\(f(d)=\sum_{i=1}^n \sum_{j=1}^m \sum_{x|i} \sum_{y|j} [\gcd(x,y)=d]\)
再设 \(F(d)=\sum_{d|k}f(k)=\sum_{i=1}^n \sum_{j=1}^m \sum_{x|i} \sum_{y|j} [d|\gcd(x,y)]\)
考虑反演
\(f(d)=\sum_{d|k}\mu(\frac kd)F(k)\)
而我们需要的答案是 \(f(1)\)
试试看

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

其实早在第二行的时候就“殊途同归”了!!

代码

f的第一中处理方式

#include<cstdio>
#include<iostream>
using namespace std;
typedef long long LL;

const int N = 50000;
int n , m , T , tot , vis[N + 5] , mu[N + 5] , prime[N + 5];
LL ans , f[N + 5];

inline void getm()
{
	mu[1] = 1;
	for(register int i = 2; i <= N; i++)
	{
		if (!vis[i]) prime[++tot] = i , mu[i] = -1;
		for(register int j = 1; prime[j] * i <= N && j <= tot; j++)
		{
			vis[prime[j] * i] = 1;
			if (i % prime[j] == 0) break;
			mu[prime[j] * i] = -mu[i];
		}
	}
	for(register int i = 2; i <= N; i++) mu[i] += mu[i - 1];
}

inline LL getf(int n)
{
	int r;
	LL res = 0;
	for(register int l = 1; l <= n; l = r + 1)
	{
		r = n / (n / l);
		res += 1LL * (r - l + 1) * (n / l);	
	}
	return res;
}

int main()
{
	getm();
	for(register int i = 1; i <= N; i++) f[i] = -1;
	scanf("%d" , &T);
	while (T--)
	{
		scanf("%d%d" , &n , &m);
		int r;
		ans = 0;
		for(register int l = 1; l <= min(n , m); l = r + 1)
		{
			r = min(n / (n / l) , m / (m / l));
			if (f[n / l] == -1) f[n / l] = getf(n / l);
			if (f[m / l] == -1) f[m / l] = getf(m / l);
			ans += f[n / l] * f[m / l] * (mu[r] - mu[l - 1]);
		}
		printf("%lld\n" , ans);
	}
}

f的第二种处理方式

#include<cstdio>
#include<iostream>
using namespace std;
typedef long long LL;

const int N = 50000;
int n , m , T , tot , vis[N + 5] , mu[N + 5] , prime[N + 5];
LL ans , f[N + 5];

inline void getm()
{
	mu[1] = 1;
	for(register int i = 2; i <= N; i++)
	{
		if (!vis[i]) prime[++tot] = i , mu[i] = -1;
		for(register int j = 1; prime[j] * i <= N && j <= tot; j++)
		{
			vis[prime[j] * i] = 1;
			if (i % prime[j] == 0) break;
			mu[prime[j] * i] = -mu[i];
		}
	}
	for(register int i = 2; i <= N; i++) mu[i] += mu[i - 1];
}

inline void getf()
{
	int r;
	for(register int i = 1; i <= N; i++)
		for(register int l = 1; l <= i; l = r + 1)
		{
			r = i / (i / l);
			f[i] += (r - l + 1) * (i / l);	
		}
}

int main()
{
	getm() , getf();
	scanf("%d" , &T);
	while (T--)
	{
		scanf("%d%d" , &n , &m);
		int r;
		ans = 0;
		for(register int l = 1; l <= min(n , m); l = r + 1)
		{
			r = min(n / (n / l) , m / (m / l));
			ans += f[n / l] * f[m / l] * (mu[r] - mu[l - 1]);
		}
		printf("%lld\n" , ans);
	}
}

实际上
关于处理 \(f\) 的办法有很多
再说一种
注意到

\[\begin{aligned} f(n) &= \sum_{i=1}^n \lfloor \frac ni \rfloor \\ &= \sum_{i=1}^n \sum_{i|d} 1 \\ &= \sum_{d=1}^n \sum_{i|d}1 \\ &= \sum_{d=1}^n \sigma_0(d) \end{aligned} \]

噢噢,其实 \(f\) 就是 \(\sigma_0\) 的前缀和
于是乎,线筛上
具体来说,约数个数定理不用白不用
线性筛时记录最小质因子的次数即可(因为只会被最小质因子筛到)。
此时预处理成了 \(O(n)\)

似乎还有杜教筛~~~

posted @ 2020-02-15 18:01  leiyuanze  阅读(311)  评论(0)    收藏  举报