P2158 [SDOI2008] 仪仗队

题目描述


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

现在,C 君希望你告诉他队伍整齐时能看到的学生人数。

分析


这道题目以这样的形式进行描述就需要我们进行数学建模了。

  1. 建立平面直角坐标系,其中 \(C\)君就站在了原点处 \(O(0,0)\),而其中 \(N\times N\)的方阵则视作 \((x,y)(x\in[0, N-1],y\in[0,N-1])\),并且\((x,y)\)一定是整数格点。。
  2. 寻找“被遮挡”的规律:
    \(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\)。
  3. 利用对称性简化计算:
    现在问题就转化为:在 \(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\)):与右下半区对称,数量完全相同。
  4. 欧拉函数建模
    看一下右下半区:固定一个 \(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)\)
posted @ 2026-09-09 20:13  chen_xing  阅读(10)  评论(0)    收藏  举报