洛谷P3327 [SDOI2015]约数个数和 莫比乌斯反演+整除分块+线性筛
洛谷P3327 [SDOI2015]约数个数和
标签
- 莫比乌斯反演
- 整除分块
- 线性筛
前言
- 这里的整除分块是另一种常见的形式。我半天都没搞清楚是怎么分的..好久之后才恍然大悟
简明题意
- \(d(x)\)表示\(x\)的约数个数。给定\(n,m\),求
思路
- 首先大家应该知道这样一个很常用的式子:
这个式子记住就好
- 我们用这个式子计算\(d(i,j)\),得出原式等于:
- \(x,y\)是\(i,j\)的因数,暴力计算因数复杂度太高了,我们去枚举\(x,y\),显然\(n,m\)的约数分别最大不超过\(n,m\),因此,原式变成:
- 对于\([gcd(x,y)==1]\)我们很快能想到用莫比乌斯函数性质替换成\(\sum\limits_{d|gcd(i,j)}\mu(d)\),而又有这样的性质:\(d|gcd(i,j)\iff d|i 且d|j\) 于是就成了:
- 换了之后就可以改为枚举\(d\),\(d\)作为\(n\)的约数,显然\(d\)的上限是\(n\)。就成了:
- 又有
推导:我们可以发现,\(\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\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\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;
}
-----------------------------------------------------------------------------------------------
朋友们,虽然这个世界日益浮躁起来,只要能够为了当时纯粹的梦想和感动坚持努力下去,不管其
它人怎么样,我们也能够保持自己的本色走下去。
—clj