题目描述 作为体育委员,C君负责这次运动会仪仗队的训练。仪仗队是由学生组成的N * N的方阵,为了保证队伍在行进中整齐划一,C君会跟在仪仗队的左后方,根据其视线所及的学生人数来判断队伍是否整齐(如下图)。
现在,C君希望你告诉他队伍整齐时能看到的学生人数。
输入格式 共一个数 N
输出格式 共一个数,即C君应看到的学生人数。
输入输出样例 输入 #1
输出 #1
说明/提示 【数据规模和约定】
对于 100% 的数据,1 ≤ N ≤ 40000
解析 来快乐推一推式子
首先考虑一下没有被看到的点长什么样 显然没有被看到的点
(x,y)
与原点的连线上一定有一点,记为
(\frac x t , \frac y t )
由于所有坐标都是整数,那么有
t \mid x, t \mid y \Leftrightarrow t \mid \gcd(x, y)
所以可以知道这个事情: 一个点
(x, y)
不可以被看到,等价于
\gcd(x, y) \neq 1
。 所以题目要求的就是所有的数对
(x,y)
的个数,满足
\gcd(x, y) = 1
分开考虑答案
首先是坐标形如
(0, y), (x, 0)
的这些点,也就是在
x
轴和
y
轴上的点,均只能看见一个
(0, 1),(1, 0)
,统计答案的时候就直接
+ 2
就行了。
然后是在直线
y = x
上的点,也只能被看见一个
(1, 1)
,统计答案的时候再来个
+ 1
。
剩下的点可以用两个
\sum
统计。 最后式子长这样(注意
\text{ans}
函数的参数值):
\begin{align}
\text{ans}(n + 1) = 3 + \sum_{x = 1}^{n} \sum_{y = 1}^{x - 1}[\gcd(x, y) = 1] + \sum_{x = 1}^{n} \sum_{y = x + 1}^{n}[\gcd(x, y) = 1]
\end{align}
看一眼这张图:
可以发现,第一种情况对应的是点
U, V
,第二种情况对应的是
W
,两个
\sum
分别对应点
A \rightarrow J
和
K \rightarrow T
的能看见的点的数量。
还是不懂? 手动模拟一下吧,看看它会统计哪些点 两个
\sum
相当于语句
1 2 3 4 5 6 7 8 ans = 0 for x = 1 to n do for y = 1 to x - 1 do ans = ans + (gcd(x,y) == 1 )for x = 1 to n do for y = x + 1 to n do ans = ans + (gcd(x,y) == 1 ) ans = ans + 3
显然可以看到的点是关于直线
y = x
对称的,所以上面的式子又可以写成
\text{ans}(n + 1) = 3 + 2\sum_{x = 1}^{n} \sum_{y = 1}^{x - 1}[\gcd(x, y) = 1]
这个式子肯定跑不过去的。把这个式子化简一下
注意到一个很常见的积性函数
\varphi(x) = \sum_{i = 1}^{x}[\gcd(i, x) = 1]
代入原式!
\text{ans}(n + 1) = 3 + 2\sum_{x = 1}^{n} \sum_{y = 1}^{x - 1}[\gcd(x, y) = 1]
= 3 + 2\sum_{x = 1}^{n} (\sum_{y = 1}^{x}[\gcd(x, y) = 1] - [\gcd(x, x) = 1])
= 3 + 2\sum_{x = 1}^{n} (\varphi(x) - [\gcd(x, x) = 1])
好了,现在就有了一个可以
O(n)
计算的式子了!
(式子要分行写是因为我博客的 Math 引擎好像炸了。。。渲染不了多行,将就看吧/kk)
最后说个事,
n = 1
的时候答案为
0
,这个很显然吧,都没有学生了(
代码实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 #include <algorithm> #include <iostream> #include <cstring> #include <cstdio> #include <vector> #define FILE_IN(__fname) freopen(__fname, "r" , stdin) #define FILE_OUT(__fname) freopen(__fname, "w" , stdout) #define rap(a,s,t,i) for (int a = s; a <= t; a += i) #define basketball(a,t,s,i) for (int a = t; a > s; a -= i) #define countdown(s) while (s --> 0) #define IMPROVE_IO() std::ios::sync_with_stdio(false) using std ::cin ;using std ::cout ;using std ::endl ;typedef long long int lli;int getint () { int x; scanf ("%d" , &x); return x; }lli getll () { long long int x; scanf ("%lld" , &x); return x; }const int MAXN = 40000 + 10 ;bool notprime[MAXN];int prime[MAXN], phi[MAXN], cnt;int n; lli ans;void Sieve () { notprime[0 ] = notprime[1 ] = true ; phi[1 ] = 1 ; for (int i = 2 ; i < MAXN; ++i) { if (!notprime[i]) { prime[++cnt] = i; phi[i] = i - 1 ; } for (int j = 1 ; j <= cnt && (i * prime[j]) < MAXN; ++j) { int x = i * prime[j]; notprime[x] = true ; if (i % prime[j] == 0 ) { phi[x] = phi[i] * prime[j]; break ; } phi[x] = phi[i] * (prime[j] - 1 ); } } }void Solve (int fn) { for (int x = 1 ; x <= fn; ++x) { ans += phi[x]; if (x == 1 ) ans -= 1 ; } }int main () { Sieve(); n = getint(); if (n == 1 ) return (0 & printf ("0\n" )); Solve(n - 1 ); printf ("%lld\n" , ans * 2 + 3 ); return 0 ; }