P1865 A % B Problem

题目理解

这道题要求我们处理多个查询,每个查询给出一个区间 [l, r],我们需要统计这个区间内质数的数量。如果查询区间超出了预先设定的范围 [1, m],则需要输出错误提示。

解题方法

  1. 预处理质数表:使用埃拉托斯特尼筛法(筛法)预处理出1到m之间的所有质数,并计算前缀和数组s[],其中s[i]表示从1到i的质数个数。

  2. 处理查询:对于每个查询,首先检查区间是否合法(在1到m范围内),如果合法则利用前缀和数组快速计算区间质数个数,否则输出错误信息。

参考程序

#include<bits/stdc++.h>
#define N 1000005  // 定义最大范围
using namespace std;

bool vis[N];    // 标记数组,vis[i]=true表示i不是质数
int pri[N],     // 存储所有质数
    s[N],       // 前缀和数组,s[i]表示1到i的质数个数
    cnt;        // 质数计数器

// 筛法求质数
void prime(int n) {
    for (int i = 2; i <= n; i++) {
        if (vis[i] == 0) {  // i是质数
            pri[++cnt] = i;  // 将i存入质数数组
            s[i] = s[i - 1] + 1;  // 更新前缀和
        } else {
            s[i] = s[i - 1];  // 不是质数,前缀和不变
        }
        // 筛去i的倍数
        for (int j = 1; j <= cnt; j++) {
            if (i * pri[j] > n) break;  // 超过范围则停止
            vis[pri[j]*i] = 1;         // 标记i*pri[j]为非质数
            if (i % pri[j] == 0) break; // 关键优化:保证每个数只被最小质因数筛去
        }
    }
}

int main() {
    int n, m;
    cin >> n >> m;  // 读取查询次数n和最大值m
    
    prime(m);  // 预处理1到m的质数
    
    while (n--) {
        int L, R;
        cin >> L >> R;  // 读取查询区间
        
        // 检查区间是否合法
        if (R > m || L < 1) {
            cout << "Crossing the line" << endl;
        } else {
            // 使用前缀和数组快速计算区间质数个数
            cout << s[R] - s[L - 1] << endl;
        }
    }

    return 0;
}

算法分析

  1. 筛法时间复杂度:预处理阶段的时间复杂度是O(n log log n),这是筛法的标准时间复杂度。

  2. 查询时间复杂度:每次查询只需要O(1)时间,因为使用了前缀和数组。

  3. 空间复杂度:使用了三个大小为N的数组,空间复杂度是O(n)。

优化点

  1. 筛法优化:代码中使用了if (i % pri[j] == 0) break这一优化,确保每个合数只被它的最小质因数筛去,这是线性筛法的关键。

  2. 前缀和优化:预处理前缀和数组使得区间查询可以在常数时间内完成。

注意事项

  1. 边界处理:需要特别注意查询区间是否在有效范围内。

  2. 数组大小:根据题目中m的最大值(1e6)来定义数组大小,避免越界。

这个解法高效地解决了问题,预处理阶段完成后,每个查询都能在常数时间内得到结果,非常适合处理大量查询的情况。

posted @ 2025-07-08 17:35  行胜于言Ibl  阅读(11)  评论(0)    收藏  举报