洛谷P3327 [SDOI2015]约数个数和 莫比乌斯反演+整除分块+线性筛

洛谷P3327 [SDOI2015]约数个数和

标签

  • 莫比乌斯反演
  • 整除分块
  • 线性筛

前言

  • 这里的整除分块是另一种常见的形式。我半天都没搞清楚是怎么分的..好久之后才恍然大悟

简明题意

  • \(d(x)\)表示\(x\)的约数个数。给定\(n,m\),求

\[\sum_{i=1}^n\sum_{j=1}^md(i*j) \]

思路

  • 首先大家应该知道这样一个很常用的式子:

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

这个式子记住就好

  • 我们用这个式子计算\(d(i,j)\),得出原式等于:

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

  • \(x,y\)\(i,j\)的因数,暴力计算因数复杂度太高了,我们去枚举\(x,y\),显然\(n,m\)的约数分别最大不超过\(n,m\),因此,原式变成:

\[\sum_{x=1}^n\sum_{y=1}^m\left([\frac nx][\frac my][gcd(x,y)==1]\right) \]

  • 对于\([gcd(x,y)==1]\)我们很快能想到用莫比乌斯函数性质替换成\(\sum\limits_{d|gcd(i,j)}\mu(d)\),而又有这样的性质:\(d|gcd(i,j)\iff d|i 且d|j\) 于是就成了:

\[\sum_{i=1}^n\sum_{j=1}^m\left([\frac ni][\frac mj]\sum\limits_{d|i且d|j}\mu(d)\right) \]

  • 换了之后就可以改为枚举\(d\)\(d\)作为\(n\)的约数,显然\(d\)的上限是\(n\)。就成了:

\[\sum_{d=1}^n\mu(d)\sum_{i=1}^n\sum_{j=1}^m\left([d|i且d|j]*[\frac ni]*[\frac mj] \right) \]

  • 又有

\[\sum\limits_{i=1}^n\sum\limits_{j=1}^m\left([d|i且d|j]*[\frac ni]*[\frac mj]\right)=\sum\limits_{i=1}^{[\frac nd]}\sum\limits_{j=1}^{[\frac md]}[\frac n{id}]*[\frac m{jd}] \]

推导:我们可以发现,\(\sum\limits_{i=1}^n\sum\limits_{j=1}^m[d|i且d|j]\),实际有效的二元组\((i,j)\),跟\(\sum\limits_{i=1}^{[\frac nd]}\sum\limits_{j=1}^{[\frac md]}\)所枚举的二元组\((i,j)\)在数量上是一样多的。在数值上,前者二元组的大小是后者的\(d\)倍。因此,变换上限后,\(i,j\)成为了原来的\(\frac 1d\),我们给它们乘上\(d\)就回到原来的项了。

  • 接下来,原式就会变成:

\[\sum_{d=1}^n\mu(d)\sum\limits_{i=1}^{[\frac nd]}\sum\limits_{j=1}^{[\frac md]}[\frac n{id}]*[\frac m{jd}] \]

  • 这个时候,对于后面的式子\(\sum\limits_{i=1}^{[\frac nd]}\sum\limits_{j=1}^{[\frac md]}[\frac n{id}]*[\frac m{jd}]\)显然是可以通过移项改成两项相乘:\(\sum\limits_{i=1}^{[\frac nd]}[\frac n{id}]\sum\limits_{j=1}^{[\frac md]}[\frac m{jd}]\),然后原式就成了:

\[\sum_{d=1}^n\mu(d)\sum\limits_{i=1}^{[\frac nd]}[\frac n{id}]\sum\limits_{j=1}^{[\frac md]}[\frac m{jd}] \]

  • 这里观察\(\sum\limits_{i=1}^{[\frac nd]}[\frac n{id}]\sum\limits_{j=1}^{[\frac md]}[\frac m{jd}]\)是可以预处理的。这里有一个小难点,就是这里未知的既有\(d\),又有\(n,m\),预处理需要枚举\(d,n,m\),那复杂度岂不是\(O(n^3)\)了?大家应该摒弃这种观念,思维不能定势。首先我们并不需要直接对\(\sum\limits_{i=1}^{[\frac nd]}[\frac n{id}]\sum\limits_{j=1}^{[\frac md]}[\frac m{jd}]\)这整个式子预处理,可以分开对\(\sum\limits_{i=1}^{[\frac nd]}[\frac n{id}]\)\(\sum\limits_{j=1}^{[\frac md]}[\frac m{jd}]\)预处理,所以现在枚举的又可以减少为\(d,m\)两种了,复杂度\(O(n^2)\)。但是现在注意观察,\(nd\)在整个式子里都是一个整体,他们整体取值的范围是确定的。因此可以直接枚举\(\frac nd\),这样预处理复杂度就是\(O(n\sqrt n)\),总复杂度就是:\(O(n\sqrt n+n*T)\)

  • 但是,这里有复杂度更低的方法,我们令\(f(x)=\sum\limits_{i=1}^x[\frac xi]\),然后原式就变成:

    \[\sum_{d=1}^n\left(\mu(d)*f([\frac nd])*f([\frac md])\right) \]

  • \(f\)的任意项是可以通过前面所说的预处理出来,从而\(O(1)\)查询。但是,这里注意到\([\frac nd]\)\([\frac md]\)是可以分块的,也就是对于一块\([l,r]\),这个区间的\([\frac nd]\)\([\frac md]\)是确定的,也就是说这个区间的\(f([\frac nd])*f([\frac md])\)是确定的。所以只需要用这个区间的\(f([\frac nd])*f([\frac md])\)乘以\(\mu(d)\)区间和就可以了,最终复杂度降低为\(O(n\sqrt n+\sqrt{n}*T)\),可以通过这一题

  • 其实还可以再做一个小优化。之前我们定义了\(f(x)=\sum\limits_{i=1}^x[\frac xi]\),如果数论比较好的同学可以立马发现,\(f(x)\)就是\([1,x]\)的约数个数之和,所以我们实际上可以线筛预处理出\(d\)函数,然后做一遍前缀和。这样优化大概会快4倍

总结

  • 对于式子\(\sum\limits_{i=1}^n\sum\limits_{j=1}^m[d|i且d|j]\)很显然它等于\(\sum\limits_{i=1}^{[\frac nd]}\sum\limits_{j=1}^{[\frac md]}1=[\frac nd]*[\frac md]\),而对于\(\sum\limits_{i=1}^n\sum\limits_{j=1}^m[d|i且d|j]*[\frac ni]*[\frac mj]\),实际上,将枚举上限分别换成\([\frac nd]\)\([\frac md]\),我们枚举的就是所有\([d|i且d|j]\)的二元组\([i,j]\)\(\frac 1d\)倍。然后我们计算\([\frac ni]*[\frac mj]\)的时候要将\(ij\)放大\(d\)倍,因此实际上我们计算的就应该是\([\frac n{id}]*[\frac m{jd}]\),所以就有:

    \[\sum\limits_{i=1}^n\sum\limits_{j=1}^m[d|i且d|j]*[\frac ni]*[\frac mj]=\sum\limits_{i=1}^{[\frac nd]}\sum\limits_{j=1}^{[\frac md]}[\frac n{id}]*[\frac m{jd}] \]

  • 对于这样的式子

    \[\sum_{i=1}^n\sum_{j=1}^mij=\left(\sum_{i=1}^ni\right)*\left(\sum_{j=1}^mj\right)$$这样可以直接将$O(n^2)$的复杂度降低为$O(n)$ \]

AC代码

#include<cstdio>
#include<algorithm>
using namespace std;

const int maxn = 50000 + 10;

bool no_prime[maxn];
int prime[maxn], mu[maxn], pre_mu[maxn], dd[maxn], num[maxn];
long long pre_dd[maxn];
int shai(int n)
{
	int cnt = 0;
	mu[1] = dd[1] = 1;

	for (int i = 2; i <= n; i++)
	{
		if (!no_prime[i])
			prime[++cnt] = i, mu[i] = -1, dd[i] = 2, num[i] = 1;

		for (int j = 1; j <= cnt && prime[j] * i <= n; j++)
		{
			no_prime[prime[j] * i] = 1;
			mu[prime[j] * i] = (i % prime[j] == 0) ? 0 : -mu[i];
			dd[prime[j] * i] = (i % prime[j] == 0) ? dd[i] / (num[i] + 1) * (num[i] + 2) : dd[i] * 2;
			num[prime[j] * i] = (i % prime[j] == 0) ? num[i] + 1 : 1;
			if (i % prime[j] == 0) break;
		}
	}

	for (int i = 1; i <= n; i++)
		pre_mu[i] = pre_mu[i - 1] + mu[i], pre_dd[i] = pre_dd[i - 1] + dd[i];
		
	return cnt;
}

long long cal2(int n, int m)
{
	int l = 1, r;
	long long ans = 0;
	while (l <= n)
	{
		r = min(n / (n / l), m / (m / l));
		ans += 1ll * (pre_mu[r] - pre_mu[l - 1]) * pre_dd[n / l] * pre_dd[m / l];
		l = r + 1;
	}
	return ans;
}

void solve()
{
	shai(maxn - 10);

	int t;
	scanf("%d", &t);
	while (t--)
	{
		int n, m;
		scanf("%d%d", &n, &m);
		if (n > m) swap(n, m);

		printf("%lld\n", cal2(n, m));
	}
}

int main()
{
	solve();
	return 0;
}
posted @ 2019-08-03 14:20  danzh  阅读(219)  评论(0编辑  收藏  举报