【题解】[SDOI2012] Longge 的问题

题目信息

题目来源:CCF 山东省选 2012;

在线评测地址:Luogu#2303

运行限制:时间不超过 \(3.00\ \textrm{s}\),空间不超过 \(128\ \textrm{MiB}\)

题目背景

Longge 的数学成绩非常好,并且他非常乐于挑战高难度的数学问题。

题目描述

现在问题来了:给定一个整数 \(n\),你需要求出 \(\sum\limits_{i=1}^n \gcd(i,n)\),其中 \(\gcd(i,n)\) 表示 \(i\)\(n\) 的最大公因数。

输入格式

输入只有一行一个整数,表示 \(n\)

输出格式

输出一行一个整数表示答案。

数据规模与约定

  • 对于 \(60\%\) 的数据,保证 \(n\le 2^{16}\)
  • 对于 \(100\%\) 的数据,保证 \(1\le n\le 2^{32}\)

分析

这道题太经典了,以至于后面的很多数论题都是模仿这个形式的。

\(60\ \texttt{pts}\)

暴力求即可,复杂度 \(\mathcal{O}(n\log n)\)

\(100\ \texttt{pts}\)

我们要推一下式子,来优化这个算法。

\[\begin{aligned} \sum\limits_{i=1}^n \gcd(i,n)=&\sum\limits_{i=1}^n \sum\limits_{d|n}d\left[[d|i]\land\left[\gcd\left(\frac{i}{d},\frac{n}{d}\right)=1\right]\right]\\ =&\sum\limits_{d|n}d\sum\limits_{d|i}\left[\gcd\left(\frac{i}{d},\frac{n}{d}\right)=1\right]\\ =&\sum\limits_{d|n}d\varphi\left(\frac{n}{d}\right) \end{aligned} \]

枚举所有因数,计算即可,复杂度 \(\mathcal{O}(\sigma(n)\sqrt{n})\),可以 AC。

Bonus

当然,这道题这么数学,其实有更好的做法,可以做到 \(\mathcal{O}(\sqrt{n})\) 的优秀复杂度。

具体内容可以看这一篇文章。

注意事项

这道题非常细节,很容易不知不觉就 WA、TLE。

边界问题

看到那个数据范围 \(\le 2^{32}\) 没有?\(n\) 是可以等于 \(2^{32}\) 的,所以别忘了开 long long

当然所有中间变量都得开 long long,否则会出现溢出。

同时,判定质数时注意平方数情况。

特判问题

\(\varphi(n)\) 时,得先判质数,然后进行质因数分解。

同时,质因数分解时循环也只能开到 \(\sqrt{n}\) 级别,最后还要特判是否有没有除掉的数。否则一个 \(2\times (2^{31}-1)\) 就能卡得飞起。

还有,你有没有把 \(1\) 判为质数呢?特判一定要加!

Code

经过了,这道题终于 \(\color{GreenYellow}{\texttt{AC}}\) 了。

#include <cstdio>
using namespace std;

typedef long long ll;

ll phi(ll x) // 计算
{
	ll ans = 1;
	bool flag = false;

	for (ll i = 2; i * i <= x; i++) // 判质数
		if (!(x % i))
		{
			flag = true;
			break;
		}
	
	if (!flag && x != 1) // 是质数
		return x - 1; // 退出

	for (ll i = 2; i * i <= x; i++) // 否则就开始找质因数
		if (!(x % i)) // 找到了
		{
			ans *= (i - 1), x /= i; // 计算贡献
			while (!(x % i))
				x /= i, ans *= i;
		}
	
	if (x != 1) // 还没算干净,一定是质数
		ans *= x - 1; // 直接计算贡献
	
	return ans; // 返回答案
}

int main()
{
	ll n, ans = 0;

	scanf("%lld", &n); // 输入
	for (ll i = 1; i * i <= n; i++) // 筛因数
		if (!(n % i)) // 找到了(一对)
		{
			ans += phi(i) * (n / i); // 计算一个
			if (i * i < n) // 不是平方数
				ans += phi(n / i) * i; // 就再计算一个。
		}
	
	printf("%lld\n", ans); // 输出

	return 0; // 然后就 AC 了、
}
posted @ 2020-08-30 10:59  5ab  阅读(143)  评论(0编辑  收藏  举报