P2158 [SDOI2008] 仪仗队
题目描述
作为体育委员,C 君负责这次运动会仪仗队的训练。仪仗队是由学生组成的 \(N \times N\) 的方阵,为了保证队伍在行进中整齐划一,C 君会跟在仪仗队的左后方,根据其视线所及的学生人数来判断队伍是否整齐(如下图)。

现在,C 君希望你告诉他队伍整齐时能看到的学生人数。
分析
这道题目以这样的形式进行描述就需要我们进行数学建模了。
- 建立平面直角坐标系,其中 \(C\)君就站在了原点处 \(O(0,0)\),而其中 \(N\times N\)的方阵则视作 \((x,y)(x\in[0, N-1],y\in[0,N-1])\),并且\((x,y)\)一定是整数格点。。
- 寻找“被遮挡”的规律:
\(C\)君看向某个学生 \((x,y)\),视线是一条从 \((0,0)\)连向 \((x,y)\)的线段。当且仅当这条线段上没有其他整数坐标点时这个学生能被完全看到。
那么如果, \((0,0)\)到 \((x,y)\)之间有其他整数点意味着什么?假设存在一个中间点 \((x',y')\),这就意味着三角形 \((0,0)\rightarrow (x',0)\rightarrow (x',y')\)与三角形 \((0,0)\rightarrow (x,0)\rightarrow (x,y)\)是相似三角形。这也意味着 \(x\)和 \(y\)可以等比例缩小,并且这个比例一定是整数,即 \(x=k\times x',y=k\times y'\rightarrow gcd(x,y)=k>1\)。
至此,我们找到了被遮挡的数学规律:除了 \((1,0)\)和 \((0,1)\)这两个贴脸的人之外,对于其他任何学生 \((x,y)\),能被看到的充要条件是 \(gcd(x,y)=1\)。 - 利用对称性简化计算:
现在问题就转化为:在 \(1\leq x\leq N-1\)且 \(1\leq y\leq N-1\)的范围内,有多少对 \((x,y)\)满足 \(gcd(x,y)=1\)。
方阵是沿着对角线 \(y=x\)对称的,我们可以把它分为三部分:- 对角线(\(x=y\)):只有 \((1,1)\)满足 \(gcd(x,y)=1\),共 \(1\)个;
- 右下半区(\(x>y\)):我们要找到 \(1\leq y< x\leq N-1\)中\(gcd(x,y)=1\)的个数;
- 左上半区(\(x<y\)):与右下半区对称,数量完全相同。
- 欧拉函数建模
看一下右下半区:固定一个 \(x\),求有多少个 \(y\)满足 \(1\leq y < x\)且 \(gcd(x,y)=1\),这本身就是欧拉函数 \(\varphi(x)\)的定义(\(\varphi(x)\)表示小于等于 \(x\)且与 \(x\)互质的数的个数)。
现在汇总一下所有能被看见的人:
- 右下半区:\(\sum_{i=2}^{N-1}\varphi(i)\);
- 左上半区:\(\sum_{i=2}^{N-1}\varphi(i)\);
- 对角线上的 \((1,1)\): \(1\)个(也可以等价为 \(\varphi(1)\));
- 坐标轴上的 \((1,0)\)和 \((0,1)\): \(2\)个
把上面的加起来,总人数为:
\[2\times (\sum_{i-2}^{N-1}\varphi(i)) + 3\leftrightarrow otal=2\times(\sum_{i=1}^{N-1}\varphi(i)) + 1
\]
由于数据规模为:\(1≤N≤40000\),因此我们可以用线性筛求解保留\(1\sim N-1\)的欧拉函数值,此后进行求和即可。
#include <iostream>
#include <vector>
int main() {
int N;
scanf("%d", &N);
std::vector<int> primes;
std::vector<bool> isPrime(N, true);
std::vector<int> phi(N, 0);
// isPrime[0] = isPrime[1] = 0;
phi[1] = 1;
for (int i = 2; i < N; i++) {
if (isPrime[i]) {
primes.push_back(i);
phi[i] = i - 1;
}
for (int pj : primes) {
if (i * pj >= N) break;
isPrime[i * pj] = false;
if (i % pj == 0) {
phi[i * pj] = phi[i] * pj;
break;
} else {
phi[i * pj] = phi[i] * phi[pj];
}
}
}
if (N == 1) {
printf("0\n");
} else {
int ans = 1;
for (int i = 1; i < N; i++) {
ans += 2 * phi[i];
}
printf("%d", ans);
}
return 0;
}
- 注意当 \(N=1\)的时候就只剩原点 \(O(0,0)\)了,此时应该返回 \(0\)而非初始化的 \(ans=1\)。
- 时间复杂度:\(O(n)\)
- 空间复杂度:\(O(n)\)

P2158 [SDOI2008] 仪仗队
浙公网安备 33010602011771号