• P1835 素数密度


    试题传送门

    一、枚举约数

    想到欧拉筛,然而我们并不能筛到(2e9),时间上C++每秒能算(1e9),(2e9)次循环肯定狒狒了。

    空间上也不允许开那么大的数组,数组最大我试过(2e8)能开,其实这都完全没有必要。因为(1e8)就是(4*100000000=400000000byte=381MB),而NOIP一般是限制(128MB),所以(1e8)都太大了。

    看来祼的欧拉筛是搞不定这道题了,也是,绿题嘛,没点难度还成?

    想想办法,因为(2e9)范围内的质数因子最大也到不了(50000)(用计算器或者(C++)代码(sqrt)一下(INT32_MAX)就知道结果~),我们可以预处理出(2sim 50000)以内的素数,然后对于每一个数,筛出质因子的倍数,剩下的就是区间内的质数啦,放一下代码:

    #include <bits/stdc++.h>
    
    using namespace std;
    typedef long long LL;
    
    const int N = 50010;
    int l, r;
    //答案
    int ans;
    
    //欧拉筛
    int primes[N], cnt;     // primes[]存储所有素数
    bool st[N];             // st[x]存储x是否被筛掉
    void get_primes(int n) {
        for (int i = 2; i <= n; i++) {
            if (!st[i]) primes[cnt++] = i;
            for (int j = 0; primes[j] <= n / i; j++) {
                st[primes[j] * i] = true;
                if (i % primes[j] == 0) break;
            }
        }
    }
    
    int main() {
        //优化一下
        ios::sync_with_stdio(false);
        cin.tie(0);
    
        //求出50000以内的质数:sqrt(INT32_MAX)=46341
        get_primes(N);
    
        //读入左右边界
        cin >> l >> r;
    
        //第十一个测试点是后加上的,导致有1 3这样的测试用例
        if (l < 2)l = 2;
    
        //开始遍历所有数字,判断是不是已知质数区间的倍数
        for (int i = l; i <= r; i++) {
            //是不是质数
            bool f = false;
            //k是i的质数因子上限
            int k = sqrt(i);
            //遍历已经筛出来的质数数组,从第一个2开始,到k结束
            for (int j = 0; primes[j] <= k; j++) {
                //i:区间内的每个数字
                //j:primes[j]代表每一个可能的质数因子
                //如果能被某个质数整除,并且它不是质数因子。那么它就是合数
                if (i % primes[j] == 0 && i / primes[j] > 1) {
                    f = true;
                    break;//是合数就不用再继续尝试了
                }
            }
            if (!f)ans++;
        }
        //输出结果
        cout << ans << endl;
    }
    

    总结一下上面的代码:
    1、(l sim r)之间的数字可以被((1sim sqrt{l}))((1sim sqrt{r}))之间的整数筛掉,因为这部分是小因子,判断小因子后,大因子那边就不用管了。

    2、外层循环在讨论((l sim r))之间的每个数字,内层循环讨论的在已经筛出的质数数组,范围是(2sim primes[j]<=k),看看(i)这个数字,是不是可以被当前的质数整除,(还不能和质数相等,这个条件要注意),如果能整除,它是合数,如果所以可能的质数因子讨论完,还没有找到可以整除的,说明(i)是质数,(ans++)

    此办法,有一个测试点TLE掉,得了91分。

    第10个测试点数据下载回来,看到:

    2146483647 2147483647

    果然够狠!数字这么大,如果按上面的思路,每次都从(2sim sqrt{2147483647})确实时间太长,超时也可以理解了。

    那该怎么办才好呢?我陷入到思考中...,上面的代码是枚举约数,超时,如果要枚举质数倍数呢?会不会有效果?

    数学知识储备
    【大于L,并且是p的倍数,最小整数是多少?】

    #include <bits/stdc++.h>
    
    using namespace std;
    typedef long long LL;
    
    int main() {
        //数学知识储备
        //【大于L,并且是p的倍数,最小整数是多少?】
        int p = 3;
        for (LL L = 100; L <= 200; L++)
            cout << max(2ll, (L - 1) / p + 1) * p << endl;
        return 0;
    }
    

    二、枚举倍数

    #include <bits/stdc++.h>
    
    using namespace std;
    typedef long long LL;
    
    //欧拉筛
    const int N = 1e5 + 10;
    int primes[N], cnt;     // primes[]存储所有素数
    bool st[N];             // st[x]存储x是否被筛掉
    void get_primes(int n) {
        for (int i = 2; i <= n; i++) {
            if (!st[i]) primes[cnt++] = i;
            for (int j = 0; primes[j] * i <= n; j++) {
                st[primes[j] * i] = true;
                if (i % primes[j] == 0) break;
            }
        }
    }
    
    //区间范围,因为我们无法完全映射所有的区间,只能采用类似于偏移的办法对某段区间整体偏移L进行描述。
    //否则空间上的限制就先达到了,无法用计算机模拟了。
    const int M = 10000010;
    int a[M];//记录偏移后的数据是不是合数,1:合数;0:质数。a[i]表示L+i是不是合数, 有一个偏移量L
    
    int main() {
        //筛出50000之内的所有质数
        get_primes(50000);//R开根号的极限也小于50000
        //问:为啥要开LL,开INT不行吗?
        //答:不行,因为下面的运算中可能存在加法,如果是极限的整数,再加一下就会爆INT。
        LL L, R;
        cin >> L >> R;
        //特判,防止第11个测试点WA掉。
        if (L == 1) L = 2;
    
        //遍历已知的质数列表
        for (int i = 0; i < cnt; i++) {
            //start:找到开始筛的数字
            //【大于L,并且是p的倍数,最小整数是多少?】
            LL start = max(2ll, (L - 1) / primes[i] + 1) * primes[i];
            //成倍的质数筛出掉
            for (LL j = start; j <= R; j += primes[i]) a[j - L] = 1; //标识为质数
        }
    
        //结果
        int ans = 0;
        for (LL i = L; i <= R; i++) if (!a[i - L])ans++;
        printf("%d", ans);
        return 0;
    }
    
  • 相关阅读:
    Github简单使用
    软件架构
    软件架构
    软件架构
    VB.net 捕获项目全局异常
    C#里面的三种定时计时器:TIMER
    深入分析委托与事件
    C#预处理器指令
    C# 实现透明可移动窗体
    多元一次方程解法 C++
  • 原文地址:https://www.cnblogs.com/littlehb/p/15191359.html
Copyright © 2020-2023  润新知