JS1k: Breathing Galaxies (1013 bytes)

数论

质数筛

埃氏筛

枚举出质数后,将它的倍数(即其倍数的合数)标记为合数,时间复杂度 \(\Theta(n\log\log n)\)

实现

节选自 OI Wiki

vector<int> prime;
bool is_prime[N];

void Eratosthenes(int n) {
  is_prime[0] = is_prime[1] = false;
  for (int i = 2; i <= n; ++i) is_prime[i] = true;
  for (int i = 2; i <= n; ++i) {
    if (is_prime[i]) {
      prime.push_back(i);
      if ((long long)i * i > n) continue;
      for (int j = i * i; j <= n; j += i)
        // 因为从 2 到 i - 1 的倍数我们之前筛过了,这里直接从 i
        // 的倍数开始,提高了运行速度
        is_prime[j] = false;  // 是 i 的倍数的均不是素数
    }
  }
}

线性筛(欧拉筛)

容易发现埃氏筛会对相同合数多次标记,让每个合数只标记一次即可。

过程:

  1. 遍历 \(i\)\(2\)\(n\)
  2. \(i\) 是质数,将 \(i\) 加入质数列表;
  3. 从小到大遍历质数列表,设当前质数为 \(p\)
    • 筛掉 \(i\times p\)
    • \(i\bmod p=0\),则退出循环。

对于最后一次操作的证明:当 \(i\) 包含因子 \(p\) 时,\(p\)\(i\) 的最小质因子,那么对于更大的质数 \(p^\prime\)\(i\times p^\prime\) 的最小质因子不是 \(p^\prime\) 而是 \(p\),不应该在这里筛掉,应在后面筛,保证每个合数只被其最小质因子筛一次。

时间复杂度 \(\Theta(n)\)

实现

void pre(int n) {
    for (int i = 2; i <= n; i++) {
        if (!nprime[i]) {
            primes.push_back(i);
        }
        for (int p : primes) {
            if (i * p > n)break;
            nprime[i * p] = 1;
            if (i % p == 0)break;
        }
    }
}

可以发现,在筛出合数的同时,线性筛也能找到每个数的最小值因子,其中 i 的最小质因子为 p

区间筛

筛出区间 \([L,R]\) 之间的质数,当 \(R\) 很大 \(R-L+1\) 很小时,单独使用线性筛难以应对,采用区间筛。

由于合数最小值因子 \(p\le\sqrt R\),线性筛筛出 \([1,\sqrt R]\) 之间的质数,枚举每个质数及其在区间 \([L,R]\) 的倍数即可。

积性函数

积性函数:对于 \(\gcd(x,y)=1\)\(x,y\),满⾜ \(f(xy)=f(x)f(y)\) 就是积性函数。

完全积性函数:对于 \(\forall x,y\),满⾜ \(f(xy)=f(x)f(y)\) 就是积性函数。

莫比乌斯函数

根据莫比乌斯函数定义有:

\(p_1\)\(n\) 的最小值因子,\(n^\prime=\frac n{p_1}\),有:

\[\mu(n)= \begin{cases} -1, & n^\prime=1\\ 0, & n^\prime\bmod p_1=0\\ -\mu(n^\prime), & \text{otherwise} \end{cases} \]

根据定义,采用线性筛,容易写出实现。

实现

节选自 OI Wiki

vector<int> pri;
bool not_prime[N];
int mu[N];

void pre(int n) {
  mu[1] = 1;
  for (int i = 2; i <= n; ++i) {
    if (!not_prime[i]) {
      mu[i] = -1;
      pri.push_back(i);
    }
    for (int pri_j : pri) {
      if (i * pri_j > n) break;
      not_prime[i * pri_j] = true;
      if (i % pri_j == 0) {
        mu[i * pri_j] = 0;
        break;
      }
      mu[i * pri_j] = -mu[i];
    }
  }
}

约数个数

\(d_i\) 表示 \(i\) 的约数个数,\(num_i\) 表示 \(i\) 最小质因子的个数。

显然:若 \(n=\prod_{i=1}^mp_i^{c_i}\),则:

\[d_i=\prod_{i=1}^m(c_i+1) \]

乘法原理即可证明。

过程:

\(x=i\times p\)

  1. \(p\nmid i\),则 \(p\)\(x\) 的新增(最小)质因子,此时 \(d_x=d_i\times(1+1)=d_i\times2\)\(num_x=1\)
  2. \(p\mid i\),则 \(p\)\(i\) 的最小质因子,此时 \(x\) 的最小值因子 \(p\) 的指数增加 \(1\),所以 \(num_x=num_i+1\)\(d_x=\frac{d_i}{num_x}\times(num_x+1)\)

实现

void pre(int n) {
    d[1] = 1;
    for (int i = 2; i <= n; i++) {
        if (!nprime[i]) {
            primes.push_back(i);
            d[i] = 2;
            num[i] = 1;
        }
        for (int p : primes) {
            if (i * p > n)break;
            nprime[i * p] = 1;
            if (i % p == 0) {
                num[i * p] = num[i] + 1;
                d[i * p] = d[i] / num[i * p] * (num[i * p] + 1);
                break;
            } else {
                num[i * p] = 1;
                d[i * p] = d[i] * 2;
            }
        }
    }
}

约数和

\(f_i\)\(i\) 所有约数和,\(g_i\) 表示 \(i\) 最小质因子的 \(\sum_{i=1}^kp_i\)

首先有:若 \(n=\sum_{i=1}^mp_i^{c_i}\),则:

\[f_i=\prod_{i=1}^m\left(\sum_{j=1}^{c_i}p_i^j\right) \]

仍然是分类讨论,设 \(x=i\times p\)

  1. \(p\nmid i\),则 \(p\)\(x\) 的新增(最小)质因子,此时 \(f_x=f_i\times(p+1)\)\(g_x=p+1\)
  2. \(p\mid i\),则 \(p\)\(i\) 的最小质因子,此时 \(x\) 的最小值因子 \(p\) 的指数增加 \(1\),所以 \(g_x=g_i\times p+1\)\(f_x=\frac{f_x}{g_i}\times g_x\)

实现

节选自 OI Wiki

vector<int> pri;
bool not_prime[N];
int g[N], f[N];

void pre(int n) {
  g[1] = f[1] = 1;
  for (int i = 2; i <= n; ++i) {
    if (!not_prime[i]) {
      pri.push_back(i);
      g[i] = i + 1;
      f[i] = i + 1;
    }
    for (int pri_j : pri) {
      if (i * pri_j > n) break;
      not_prime[i * pri_j] = true;
      if (i % pri_j == 0) {
        g[i * pri_j] = g[i] * pri_j + 1;
        f[i * pri_j] = f[i] / g[i] * g[i * pri_j];
        break;
      }
      f[i * pri_j] = f[i] * f[pri_j];
      g[i * pri_j] = 1 + pri_j;
    }
  }
}

例题

P6810 「MCOI-02」Convex Hull 凸包

\(d(i)=\tau(i)\),由题意得:

\[\begin{aligned} S&=\sum_{i=1}^n\sum_{j=1}^md(i)d(j)d(gcd(i,j))\\ &=\sum_{i=1}^n\sum_{j=1}^md(i)d(j)\sum_{g\mid n,g\mid m}1\\ &=\sum_{g=1}^{min(n,m)}\left(\sum_{g\mid i}d(i)\right)\left(\sum_{g\mid j}d(j)\right)\\ &=\sum_{g=1}^{\min(n, m)} \left( \sum_{x=1}^{\lfloor n/d \rfloor} d(gx) \right) \left( \sum_{y=1}^{\lfloor m/g \rfloor} d(gy) \right) \end{aligned} \]

预处理 \(d(n)\) 即可,时间复杂度 \(\Theta(n\log n)\)

关于时间复杂度的证明:

假设 \(n<m\),总操作次数:

\[\begin{aligned} T &= \sum_{d=1}^{n} \left( \frac{n}{d} + \frac{m}{d} \right)\\ &=\sum_{d=1}^{n}\left(\frac{n}{d}\right)+\sum_{d=1}^{n}\left(\frac{m}{d}\right)\\ &=n \sum_{d=1}^{n} \frac{1}{d} + m \sum_{d=1}^{n} \frac{1}{d}\\ &=(n + m) \sum_{d=1}^{n} \frac{1}{d}\\ &\approx(n+m)\log n \end{aligned} \]

实现

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e6 + 5;
int n, m, mod, d[N], num[N];
bool nprime[N];
vector <int> primes;
void init(){
	d[1] = 1;
	for (int i = 2; i <= m; ++i){
		if (!nprime[i]){
			primes.push_back(i);
			d[i] = 2;
			num[i] = 1;
		}
		for (auto p : primes){
			if (i * p > m)break;
			nprime[i * p] = 1;
			if (i % p == 0){
				num[i * p] = num[i] + 1, d[i * p] = d[i] / num[i * p] * (num[i * p] + 1);
				break;
			}
			num[i * p] = 1;
			d[i * p] = d[i] * 2;
		}
	}
}
signed main(){
	cin.tie(0)->sync_with_stdio(0);
	cin >> n >> m >> mod;
	if (n > m)swap(n, m);
	init();
	int ans = 0;
	for (int g = 1; g <= n; ++g){
		int sx, sy;
		sx = sy = 0;
		for (int x = 1; g * x <= n; ++x){
			(sx += d[g * x]) %= mod;
		}
		for (int y = 1; g * y <= m; ++y){
			(sy += d[g * y]) %= mod;
		}
		(ans += sx * sy % mod) %= mod;
	}
	cout << ans;
	return 0;
} 

狄利克雷卷积

\[h(n)=\sum_{d\mid n}f(d)g(\frac nd) \]

记作 \(h=f*g\)

性质

\(f, g, h\) 都是数论函数. 那么, 有:

  1. 交换律: \(f * g = g * f\).
  2. 结合律: \((f * g) * h = f * (g * h)\).
  3. 分配律: \((f + g) * h = f * h + g * h\).
  4. 单位元: \(f * \varepsilon = \varepsilon * f = f\), 其中, \(\varepsilon(n) = [n = 1]\) 是卷积单位元, \([\cdot]\) 是 Iverson 括号.
  5. 逆元: 当且仅当 \(f(1) \neq 0\) 时, 存在 \(g\) 使得 \(f * g = g * f = \varepsilon\), 且 \(g\) 称为 \(f\)Dirichlet 逆元 (Dirichlet inverse), 可以记作 \(f^{-1}\). 而且, 逆元 \(g\) 满足递推公式

\[g(n) = \frac{\varepsilon(n) - \sum_{k\ell=n, k \neq 1} f(k)g(\ell)}{f(1)}, \]

节选自 OI Wiki

狄利克雷卷积前缀和

P5495 【模板】Dirichlet 前缀和

先用线性筛筛出 \([1,n]\) 的所有质数。

枚举 \(i\)\(1\)\(\frac np\),执行:

\[a_{i\times p}=a_{i\times p}+a_i \]

这样使 \(a_i\) 的值累加到 \(i\) 乘若干质数 \(p\) 得到的倍数上,经过所有质数的处理后,可以得到答案(分解质因数)。

时间复杂度 \(\Theta(n\log\log n)\),证明较为复杂,不再赘述。

**由此可见,狄利克雷卷积前缀和

实现

for (auto p : primes){
	for (int i = 1; i * p <= n; ++i){
		a[i * p] += a[i];
	} 
}

关于狄利克雷卷积前后缀和

我们发现,当题目中出现形如:

\[F(d)=\sum_{x\mid d}g(x) \]

的式子时,可以考虑使用狄利克雷卷积前缀和优化。

类似地,若式子形如:

\[F(d)=\sum_{d\mid x}g(x) \]

则可以考虑后缀和。

例题

P6810 「MCOI-02」Convex Hull 凸包

还是这道题,我们观察倒数第二步的式子:

\[S=\sum_{g=1}^{min(n,m)}\left(\sum_{g\mid i}d(i)\right)\left(\sum_{g\mid j}d(j)\right) \]

后两个式子可以使用狄利克雷卷积后缀和优化,时间复杂度 \(\Theta(n\log\log n)\)

只需把主函数部分代码修改为如下即可。

init();
for (int i = 1; i <= n; ++i){
	a[i] = d[i];
}
for (int i = 1; i <= m; ++i){
	b[i] = d[i];
}
int ans = 0;
for (auto p : primes){
	for (int i = n / p; i >= 1; --i){
		a[i] += a[i * p];
		a[i] %= mod;
	}
}
for (auto p : primes){
	for (int i = m / p; i >= 1; --i){
		b[i] += b[i * p];
		b[i] %= mod;
	}
}
for (int i = 1; i <= n; ++i){
	ans += a[i] * b[i] % mod;
	ans %= mod;
} 
cout << ans;

Möbius 函数(莫比乌斯函数)

莫比乌斯函数

上面已经介绍由莫比乌斯函数定义得到的,更方便处理的式子,接下来给出莫比乌斯函数的严格定义:

\[\mu(n)= \begin{cases} 1, & n = 1\\ 0, & n\text{ is divisible by a square}>1\\ (−1)^k, & n\text{ is the product of k distinct primes.} \end{cases} \]

具体地:

\(n=\prod_{i=1}^kp_i^{c_i}\),其中 \(p_i\) 是质数,\(c_i\in\mathbb{N^+}\)

  1. \(n=1\),则 \(\mu(n)=1\)
  2. \(\exists c_i>1\),则 \(\mu(n)=0\)
  3. 否则,\(\forall c_i=1\)\(\mu(n)=(-1)^k\),即当 \(n\) 有奇数个不同的质因子时,\(\mu(n)=-1\),偶数个即为 \(\mu(n)=1\)

性质

对于正整数 \(n\),有:

\[\sum_{d\mid n}\mu(d)=[n=1]= \begin{cases} 1,& n = 1\\ 0,& n \ne 1 \end{cases} \]

二项式定理可以证明,具体可参考 OI Wiki

利用狄利克雷卷积,容易发现 \(\varepsilon=\mathbf1*\mu\)。也就是说,莫比乌斯函数是常值函数 \(\mathbf1\) 的狄利克雷逆。

莫比乌斯反演

先给出式子:

\(f(n),g(n)\) 是两个数论式子,那么有:

\[f(n)=\sum_{d\mid n}g(d)\iff g(n)=\sum_{d\mid n}\mu(\frac nd)f(d) \]

直接推导较为复杂,考虑简单证法:

由狄利克雷卷积,原式等价于:

\[f=\mathbf1*g\iff g=\mu*f \]

\(\mu\) 函数和狄利克雷卷积的性质,对左边式子进行变形:

\[f*\mu=(\mathbf1*g)*\mu=(\mathbf1*\mu)*g=\varepsilon*g=g \]

反之易证。

这是考虑它的因数的形式,考虑倍数形式也可:

\[f(n)=\sum_{n\mid d}g(d)\iff g(n)=\sum_{n\mid d}\mu(\frac dn)f(d) \]

另外还有一个重要形式:

\[[gcd(i,j)=1]=\sum_{k|i,k|j}\mu(k) \]

更多拓展形式可见 OI Wiki

例题

P3911 最小公倍数之和

\(c(i)\) 表示 \(i\) 的出现次数,\(M\) 表示 \(\max_{i=1}^na_i\),则

\[\begin{aligned} S &= \sum_{i=1}^M \sum_{j=1}^M \text{lcm}(i, j) \times c(i) \times c(j) \\ &= \sum_{i=1}^M \sum_{j=1}^M c(i)c(j) \times \frac{ij}{\gcd(i, j)} \\ &= \sum_{d=1}^M \sum_{i=1}^M \sum_{j=1}^M c(i)c(j) \times \frac{ij}{d} \times [\gcd(i, j) = d] \\ &= \sum_{d=1}^M \sum_{x=1}^{\lfloor \frac Md \rfloor} \sum_{y=1}^{\lfloor \frac Md \rfloor} c(dx)c(dy) \times d \times x \times y \times [\gcd(x, y) = 1]\\ &= \sum_{d=1}^M d \sum_{x=1}^{\lfloor \frac Md \rfloor} \sum_{y=1}^{\lfloor \frac Md \rfloor} c(dx)c(dy)\times x \times y \sum_{k|\gcd(x, y)} \mu(k)\\ &= \sum_{d=1}^M d \sum_{k=1}^{\lfloor \frac Md \rfloor} \mu(k) \sum_{x'=1}^{\lfloor \frac {M}{dk} \rfloor} \sum_{y'=1}^{\lfloor \frac {M}{dk} \rfloor} c(dkx') c(dky') \times (k x') \times (k y') \\ &= \sum_{d=1}^M d \sum_{k=1}^{\lfloor \frac Md \rfloor} \mu(k) \times k^2 \left( \sum_{t=1}^{\lfloor \frac {M}{dk} \rfloor} t \times c(dk t) \right)^2\\ &= \sum_{T=1}^M T \times \left( \sum_{k|T} \mu(k) \times k \right) \times F(T)^2 \end{aligned} \]

\[F(T)=\sum_{x=1}^{\lfloor\frac MT \rfloor} x \times c(T \times x) \]

关于最后一步的由来:

先给出一个等式,若 \(f(i,j)\) 是一个数论函数。

\[\sum_{d=1}^M\sum_{k=1}^{\lfloor\frac Md\rfloor}f(d,k)=\sum_{T=1}^M\sum_{d|T}f(d,\frac Td) \]

因为左右两边都是枚举 \(d\times k\le M\) 的二元组 \((d,k)\),所以是等价的。

最终的式子中,\(T\times F(T)^2\) 改写为 \(\frac{(T\times F(T))^2}{T}\),注意到分母可以转化为:

\[\sum_{x=1}^{\lfloor\frac MT \rfloor} T\times x \times c(T \times x)=\sum_{p\mid M}p \times c(p) \]

考虑使用狄利克雷卷积后缀和优化。

最终式子中,括号中的式子是可以预处理的,所以最终时间复杂度为 \(\Theta(m\log m+m\log\log m)\)

实现

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 5e4 + 5;
int n, m, x, c[N], mu[N], a[N], g[N];
bool nprime[N];
vector <int> primes;
void init(){
	mu[1] = 1;
	for (int i = 2; i <= m; ++i){
		if (!nprime[i]){
			primes.push_back(i);
			mu[i] = -1;
		}
		for (auto p : primes){
			if (i * p > m)break;
			nprime[i * p] = 1;
			if (i % p == 0){
				mu[i * p] = 0;
				break;
			}
			mu[i * p] = -mu[i];
		}
	}
}
signed main(){
	cin.tie(0)->sync_with_stdio(0);
	cin >> n;
	for (int i = 1; i <= n; ++i){
		cin >> x;
		++c[x];
		m = max(m, x);
	}
	init();
	for (int k = 1; k <= m; ++k){
		for (int T = k; T <= m; T += k){
			a[T] += k * mu[k];
		}
	}
	for (int i = 1; i <= m; ++i){
		g[i] = i * c[i];
	}
	for (auto p : primes){
		for (int i = m / p; i >= 1; --i){
			g[i] += g[i * p];
		}
	}
	int ans = 0;
	for (int T = 1; T <= m; ++T){
		int f = g[T] / T;
		ans += T * a[T] * f * f;
	}
	cout << ans;
	return 0;
} 
posted @ 2026-07-30 10:11  __int127  阅读(20)  评论(0)    收藏  举报