P1403 [AHOI2005] 约数研究

题目原意是设\(f(i)=d(i)\),即表示自然数\(i\)的约数个数,题目给定输入\(n\),需要我们计算出\(\sum_{i=1}^nf(i)\)进行返回。

解析

解法一

数论函数
结合线性筛会利用合数的最小质因数筛去合数,并且约数个数的函数属于积性函数。

#include <iostream>
#include <vector>

int main() {
	int n;
	std::cin >> n;
	
	std::vector<int> primes;
	std::vector<bool> isPrime(n + 1, true);
	std::vector<int> d(n + 1, 0);
	std::vector<int> num(n + 1, 0);
	
	isPrime[0] = isPrime[1] = false;
	d[1] = 1;
	
	for (int i = 2; i <= n; i++) {
		if (isPrime[i]) {
			primes.push_back(i);
			num[i] = 1;
			d[i] = 2;
		}
		
		for (int pj : primes) {
			if (i * pj > n) break;
			isPrime[i * pj] = false;
			
			if (i % pj == 0) {
				num[i * pj] = num[i] + 1;
				d[i * pj] = d[i] / (num[i] + 1) * (num[i * pj] + 1);
				break;
			} else {
				num[i * pj] = 1;
				d[i * pj] = d[i] * 2;
			}
		}
	}
	int ans = 0;
	for (int di : d) ans += di;
	std::cout << ans << std::endl;
	return 0;
}

解法二

\(f(n)\)表示正整数\(n\)的因数个数,也可以理解为枚举\(n\)的因数\(d\),每枚举一个因数\(d\),\(f(n)\)加\(1\)。两种数学解释对应着两种不同的表达式,即\(f(n)=\sum_{d|n}1\),或\(f(n)=\sum_{d=1}^n[d|n]\)。后者的语义是\([d|n]\)是一个判断正误的函数,正确为\(1\),错误为\(0\)。

题目要求我们求解\(I=\sum_{i=1}^nf(i)\),将\(f(i)\)的定义式代入:

\[\begin{flalign} & I=\sum_{i=1}^n\sum_{d|i}1=\sum_{i=1}^n\sum_{d=1}^i[d|i]\tag{1} \end{flalign} \]

求和符号可以交换顺序,把\(d\)的枚举提前:\(I=\sum_{d=1}^n\sum_{i=1}^n[d|i]\)
相当于分别考虑\(d=1\)时,对\(i=1,2,3,\cdots,n\)的贡献;\(d=2\)时,对\(i=1,2,3,\cdots,n\)的贡献;\(\cdots\);\(d=n\)时,对\(i=1,2,3,\cdots,n\)的贡献。
对于一个固定的\(d\),\(\sum_{i=1}^n[d|n]\)的意义就是说明在\(1\sim n\)中,有多少个数字有\(d\)这个约数,即有多少个数是\(d\)的倍数,\(\sum_{i=1}^n[d|n]=\lfloor {n\over d}\rfloor\)。

结合\((1)\),我们得到了\(I=\sum_{d=1}^n\lfloor{n\over d}\rfloor\)的结论。这种思路叫做“算两次”或“调换求和顺序”,在数论问题(特别是结合莫比乌斯反演时)的求解中是很不错的想法。

#include <iostream>

int main() {
	int n;
	std::cin >> n;
	int ans = 0;
	for (int i = 1; i <= n ;i++) {
		ans += n / i;
	}
	std::cout << ans << std::endl;
	return 0;
}
posted @ 2026-09-02 15:06  chen_xing  阅读(13)  评论(0)    收藏  举报