题目链接

[SDOI2008]仪仗队

题目描述

在一个 的学生方阵中,观察者站在左后方(坐标原点 )。方阵的学生坐标为 ,其中

一个位于点 的学生能被看到,当且仅当连接原点 和点 的线段上没有其他学生。你需要计算总共能看到多少名学生。

输入:

  • 输入一行,包含一个整数

输出:

  • 输出一行,表示能看到的学生总数。

注意:根据题目样例(,输出 ),我们可以推断出实际的方阵是从 的区域,观察者位于 ,并且不计自身。因此,我们需要统计坐标 范围内( 不同时为 )的可见点。

解题思路

这道题本质上是一个经典的数论问题,与欧拉函数紧密相关。

1. 可见性条件

一个位于点 的学生能被观察者(位于 )看到,意味着线段 中间没有其他整数坐标点。 如果坐标 有一个大于 1 的公约数 ,那么点 就是一个在原点和 之间的整数点,它会挡住观察者的视线。 因此,点 可见的充要条件

2. 问题转化

问题转化为:在一个 的网格区域中(坐标从 ),有多少个点 满足 ,且 不同时为

3. 利用对称性求解

由于方阵是正方形,问题具有对称性。我们可以分开统计:

  1. 坐标轴上的点

    • 轴上,满足 的点只有
    • 轴上,满足 的点只有
    • 这两个点是肯定可见的。
  2. 对角线上的点

    • 的对角线上,满足 的点只有
    • 这个点也是肯定可见的。
  3. 其他区域的点

    • 除去坐标轴和对角线,剩下的区域被对角线 分为两个完全对称的部分(一个满足 ,另一个满足 )。
    • 我们只需要计算其中一部分(例如 )的可见点数量,然后乘以 2 即可。
    • 对于一个固定的 ),我们需要找有多少个 () 满足 。这个数量正好是欧拉函数 的定义(因为当 时, ,所以统计 的结果是一样的)。
    • 所以,在 区域内,可见点的总数是

4. 最终公式

综合以上几部分,可见学生的总数为:

  • 时,方阵里没有学生,答案为
  • 时,总数 = (坐标轴上的点) + (对角线上的点) + (其他区域的点)

5. 算法实现

为了计算 ,我们需要高效地求出 的所有欧拉函数值。这可以通过线性筛 的时间内完成。

  1. 写一个线性筛,预处理出 的所有 值。
  2. 根据公式,累加 的值并计算最终结果。

代码

#include <iostream>
#include <vector>
#include <numeric>

using namespace std;

const int MAXN = 40005;
int phi[MAXN];
int primes[MAXN], cnt;
bool is_prime[MAXN];

void euler_sieve(int n) {
    fill(is_prime, is_prime + n + 1, true);
    is_prime[0] = is_prime[1] = false;
    phi[1] = 1;
    for (int i = 2; i <= n; i++) {
        if (is_prime[i]) {
            primes[cnt++] = i;
            phi[i] = i - 1;
        }
        for (int j = 0; j < cnt && i * primes[j] <= n; j++) {
            is_prime[i * primes[j]] = false;
            if (i % primes[j] == 0) {
                phi[i * primes[j]] = phi[i] * primes[j];
                break;
            } else {
                phi[i * primes[j]] = phi[i] * (primes[j] - 1);
            }
        }
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(NULL);

    int n;
    cin >> n;

    if (n == 1) {
        cout << 0 << endl;
        return 0;
    }

    euler_sieve(n - 1);

    long long sum_phi = 0;
    for (int i = 2; i < n; i++) {
        sum_phi += phi[i];
    }

    cout << 2 * sum_phi + 3 << endl;

    return 0;
}
import java.util.Scanner;
import java.util.Arrays;

public class Main {
    static final int MAXN = 40005;
    static int[] phi = new int[MAXN];
    static int[] primes = new int[MAXN];
    static boolean[] is_prime = new boolean[MAXN];
    static int cnt = 0;

    public static void eulerSieve(int n) {
        Arrays.fill(is_prime, true);
        is_prime[0] = is_prime[1] = false;
        phi[1] = 1;
        for (int i = 2; i <= n; i++) {
            if (is_prime[i]) {
                primes[cnt++] = i;
                phi[i] = i - 1;
            }
            for (int j = 0; j < cnt && (long) i * primes[j] <= n; j++) {
                is_prime[i * primes[j]] = false;
                if (i % primes[j] == 0) {
                    phi[i * primes[j]] = phi[i] * primes[j];
                    break;
                } else {
                    phi[i * primes[j]] = phi[i] * (primes[j] - 1);
                }
            }
        }
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();

        if (n == 1) {
            System.out.println(0);
            return;
        }

        eulerSieve(n - 1);

        long sumPhi = 0;
        for (int i = 2; i < n; i++) {
            sumPhi += phi[i];
        }

        System.out.println(2 * sumPhi + 3);
    }
}
import sys

def euler_sieve(n):
    phi = list(range(n + 1))
    is_prime = [True] * (n + 1)
    is_prime[0] = is_prime[1] = False
    primes = []
    phi[1] = 1

    for i in range(2, n + 1):
        if is_prime[i]:
            primes.append(i)
            phi[i] = i - 1
        
        for p in primes:
            if i * p > n:
                break
            is_prime[i * p] = False
            if i % p == 0:
                phi[i * p] = phi[i] * p
                break
            else:
                phi[i * p] = phi[i] * (p - 1)
    return phi

def main():
    n = int(sys.stdin.readline())

    if n == 1:
        print(0)
        return

    phi_values = euler_sieve(n - 1)
    
    sum_phi = 0
    for i in range(2, n):
        sum_phi += phi_values[i]
        
    print(2 * sum_phi + 3)

if __name__ == "__main__":
    main()

算法及复杂度

  • 算法:欧拉函数 + 线性筛
  • 时间复杂度:使用线性筛预处理欧拉函数表的时间复杂度为 。计算总和的循环也是 。因此,总时间复杂度为
  • 空间复杂度:需要一个数组来存储欧拉函数值以及线性筛所需的辅助数组,空间复杂度为