0x31质数

判定

0-sqrt(n)内看能不能整除。
证明:反证法

筛选

(1)最最朴素想法:每个都判定一下,时间复杂度O(nsqrt(n))
(2)eratosthenes法:接近O(n),比其大一点
对于比n小的质数x 从小到大枚举,x,2x,3x…等都显然不是质数。依次标记。
优化:x…,(x-1)显然已经被前边的筛没了,故每次从x*x开始

(3)线性筛法:O(n)
对于合数例如12=2x6=3x4会被筛2次。
若让每个数只被他的最小质因子筛掉,则显然每个数就只会被处理一次了。这是因为显然一个数不可能有两个最小质因子。
因此理解如下:
遍历每个数,
若没有被标记过,质数数组加一个。
我们这样理解:我们只需要对于每个质数,找到2-n中以这个质数作为最小质因子的数(当然不包括自己),把他去掉即可。
由于我们已经有了一层倍数i的循环了。-(当然这个原始目的是为了吧所有数据判断一遍,但功能一样不妨合并。)
即不妨我们这样想:
找到了一个质数p,则p*i肯定全是合数。
若让p作为最小质因子筛的话,i满足 i的所有除了1以外的质因子是大于p的。
因此,遍历i,如果其没被标记过,则其是质数。
然后(从小到大)遍历已知的质数,只带i%p==0.这个时候,如果继续往下遍历则可能出现不满足题意的p

int primes[N], cnt;
bool st[N];

void get_primes(int n)
{
    for (int i = 2; i <= n; i ++ )
    {
        if (!st[i]) primes[cnt ++ ] = i;//如果i没有被标记过,其为质数
        for (int j = 0; primes[j] <= n / i; j ++ )//一定要有等号
        {
            st[primes[j] * i] = true;
            if (i % primes[j] == 0) break;
        }
    }
}

(4)当题目出现求[L,R]内的所有质数时,不妨先用线性筛法求sqrt(R)内的质数,然后再用eratosthenes法算,这样时间复杂度要小于直接线性筛法

质数距离

知识点

1、上取整与下取整的关系:
⌈ a b ⌉ \lceil \frac{a}{b} \rceil ba= ⌊ a + b − 1 b ⌋ \lfloor \frac{a+b-1}{b} \rfloor ba+b1
理解:
不妨设 a=kb+r(0<=r<=b-1)
左右同时正除以b,推出 ⌊ a b ⌋ \lfloor \frac{a}{b} \rfloor ba=k
而a+b-1=(k+1)b+r-1(0<=r<=b-1)
左右同时正除以b,
若r=0,推出 ⌊ a + b − 1 b ⌋ \lfloor \frac{a+b-1}{b} \rfloor ba+b1=k
若r>0,推出 ⌊ a + b − 1 b ⌋ \lfloor \frac{a+b-1}{b} \rfloor ba+b1=k+1
而这恰好就是上取整
2、如何找到第一个>=l的第一个p的倍数呢?
x= ⌈ l p ⌉ ∗ p \lceil \frac{l}{p} \rceil *p plp
3、不超过n的质数大概有 n l n n \frac{n}{ln n} lnnn

做法

线性筛法是O(n)级别的,大概可处理10^9个的操作。
显然能直接从1-R进行线性筛法。
因此考虑二次筛法,先计算根号n内的数,然后再进行一次筛选。

注解

ly20 同学在y总的代码 问的3个很高质量的问题,也是很细节的地方,感谢ly20 同学

(1)每个质数是2~50000中的数,为啥 LL p = primes[i]; 这里的p的LL啊, int 不就够了吗?
(2)for (LL j = max(p * 2, (l + p - 1) / p * p); j <= r; j += p) 这里的 j 是小于等于 r 的,而 r 的取值范围是小于2 ^ 31,也是在int 范围内啊, 这里为啥用LL啊?
(3) 我知道这里是复用st数组,但是在用之前都初始化为0了啊,为什么init(50000); 放在while (cin >> l >> r) 的外面(也就是最前面)代码不行啊?
回答:

(1)LL p = primes[i],这里p用LL是因为如果p也是用int类型,本身l也是用int类型,如果l取得足够大,下面的l + p - 1会有可能直接爆int变成负数
(2)这里的 j 是小于等于 r 的,而 r 的取值范围是小于2 ^ 31,这里确实是这样,可是这个循环跳出的条件是j <= r,也就是说如果r是最大的int,那么当j += p,要超过最大的int的时候需要比它还大才能跳出循环,因此直接爆int变成负数,然后j <= r依然成立,会一直死循环下去
(3)放在最前面也是可以的,y总的代码中判断从[1,50000]中谁是质数和在区间[L,R]中谁是质数直接复用st[]数组,就不用再开一个数组去存了,也可以把init()放在前面,用一个专门的数组去记录区间[L,R]中谁是质数
时间复杂度 O(n)

作者:小呆呆
链接:https://www.acwing.com/solution/acwing/content/11586/
来源:AcWing

代码

#include<iostream>
#include<cstring>
using namespace std;
const int N=1000010;
typedef long long ll;
int st[N],primes[N],cnt;

ll L,R;
void init(int n)
{
    memset(st,0,sizeof(st));
    cnt=0;
    for(int i=2;i<=n;i++)
    {
        if(!st[i])primes[cnt++]=i;
        for(int j=0;primes[j]*i<=n;j++)
        {
            st[i*primes[j]]=true;
            if(i%primes[j]==0)break;
        }
    }
}


int main()
{
    
    while(cin>>L>>R)
    {
        init(50000);
        memset(st,0,sizeof(st));
        for(int i=0;i<cnt;i++)
        {
            ll p=primes[i];
            for(ll j=max(2*p,((L+p-1)/p)*p);j<=R;j+=p)
            {
                st[j-L]=true;
            }
        }
        cnt = 0;
        for (int i = 0; i <= R - L; i ++ )
            if (!st[i] && i + L >= 2)
                primes[cnt ++ ] = i + L;

        if (cnt < 2) puts("There are no adjacent primes.");
        else
        {
            int minp = 0, maxp = 0;
            for (int i = 0; i + 1 < cnt; i ++ )
            {
                int d = primes[i + 1] - primes[i];
                if (d < primes[minp + 1] - primes[minp]) minp = i;
                if (d > primes[maxp + 1] - primes[maxp]) maxp = i;
            }

            printf("%d,%d are closest, %d,%d are most distant.\n",
                primes[minp], primes[minp + 1],
                primes[maxp], primes[maxp + 1]);
    }
    }
}


阶乘分解

做法

直接做是O(n*sqrt(n))复杂度过高;
转换思路,1-n所有的数质因数分解后一定不可能大于n;
然后只需要统计含有质因子p的个数之和即可。
首先先找到所有的质数,
对于每个质数p,
至少包含一个质因子p的个数有 ⌊ n p ⌋ \lfloor{\frac{n}{p}}\rfloor pn
至少包含两个质因子p的个数有 ⌊ n p 2 ⌋ \lfloor{\frac{n}{p^2}}\rfloor p2n
以此类推。
由于我们关心的是p的个数,而恰好两个包含两个p的数,其中有部分已经被前边统计过了。
实际上最后的p的个数即为 ∑ ⌊ n p i ⌋ \sum{\lfloor{\frac{n}{p^i}}\rfloor} pin

代码

#include<iostream>

using namespace std;
const int N=1000000;

int cnt;
int primes[N],st[N];
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[i*primes[j]]=true;
            if(i%primes[j]==0)break;
        }
    }
}

int main()
{
    int n;
    cin>>n;
    get_primes(n);
    for(int j=0;j<cnt;j++)
    {
        int p=primes[j];
        int t=n,s=0;
        while(t)t=t/p,s+=t;
        cout<<p<<" "<<s<<endl;
    }
    
    
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值