acm数论之旅(转载)--素数

https://www.cnblogs.com/linyujun/p/5198832.html

前言:好多学ACM的人都在问我数论的知识(其实我本人分不清数学和数论有什么区别,反正以后有关数学的知识我都扔进数论分类里面好了)

于是我就准备写一个长篇集,把我知道的数论知识和ACM模板都发上来(而且一旦模板有更新,我就直接在博客上改了,所以记得常来看看(。・ω・))

 

 

废话说完了,直接进入正题ヾ(=^▽^=)ノ

 

 

 

素数,又叫质数,定义是除了1和它本身以外不再有其他的因数

我们通过这个定义,可以写如下程序判断一个数是不是质数

复制代码
1 bool prime(int x){//判断x是不是质数,是返回true,不是返回false 
2     if(x <= 1) return false; 
3     for(int i = 2; i < x; i ++){
4         if(x % i == 0) return false;
5     }
6     return true;
7 }
复制代码

 

这个程序的时间复杂度是O(n),有没有更快的方法,当然

看这个

复制代码
 1 bool prime(int x){//判断x是不是质数,是返回true,不是返回false 
 2     if(x <= 1) return false; 
 3     for(int i = 2; i <= sqrt(x + 0.5); i ++){//0.5是防止根号的精度误差 
 4         if(x % i == 0) return false;
 5     }
 6     return true;
 7 }
 8 //另一种方法,不需要根号 
 9 bool prime(int x){//判断x是不是质数,是返回true,不是返回false 
10     if(x <= 1) return false; 
11     for(int i = 2; i * i <= x; i ++){//用乘法避免根号的精度误差 
12         if(x % i == 0) return false;
13     }
14     return true;
15 }
16 //根据题目不同,如果i*i会爆int,记得开longlong 
复制代码

 

这个复杂度是O(√n),速度快多了(#°Д°)

 

 

 

 

 

 

 

 

根据题目不同,有可能你需要先预处理出1~N这N个数是否是素数

如果用刚刚的方法,复杂度就是O(n√n)

复制代码
 1 #include<cstdio>
 2 const int N = 100000 + 5;
 3 bool prime[N];
 4 bool is_prime(int x){
 5     if(x <= 1) return false; 
 6     for(int i = 2; i * i <= x; i ++){
 7         if(x % i == 0) return false;
 8     }
 9     return true;
10 }
11 void init(){
12     for(int i = 0; i < N; i ++){
13         prime[i] = is_prime(i);
14     }
15 }
16 int main(){
17     init();
18 }
复制代码

 

 

如果n大一点,就太慢了(。・ω・)ノ゙

 

介绍一种新方法,埃筛

 

埃筛--------------埃拉托斯特尼筛法,或者叫埃氏筛法

 

原理:如果找到一个质数,那么这个质数的倍数都不是质数

 

比如2是质数,那么4,6,8,10,12...都不是质数

然后看3是质数,那么6,9,12,15,18,21...都不是质数

然后看4,4已经被2标记为合数了,所以跳过

然后看5......这样一直筛下去

复制代码
 1 #include<cstdio>
 2 const int N = 100000 + 5;
 3 bool prime[N];
 4 void init(){
 5     for(int i = 2; i < N; i ++) prime[i] = true;//先全部初始化为质数 
 6     for(int i = 2; i < N; i ++){
 7         if(prime[i]){//如果i是质数 
 8             for(int j = 2*i; j < N; j += i){//从i的两倍开始的所有倍数 
 9                 prime[j] = false; 
10             }
11         }
12     }
13 }
14 int main(){
15     init();
16 }
复制代码

 

因为一些数字,比如6既被2的for循环经过又被3的for循环经过,所以复杂度不是O(n)

这个复杂度经过专业人士检验,复杂度O(nloglogn)(学过高数的小朋友可以自己证明≖‿≖✧当然也可以去百度)

 

知道原理后,我们再稍微优化一下就更快了

复制代码
 1 #include<cstdio>
 2 const int N = 100000 + 5;
 3 bool prime[N];
 4 void init(){
 5     for(int i = 2; i < N; i ++) prime[i] = true;
 6     for(int i = 2; i*i < N; i ++){//判断改成i*i<N 
 7         if(prime[i]){
 8             for(int j = i*i; j < N; j += i){//从i*i开始就可以了 
 9                 prime[j] = false;  
10             }
11         }
12     }
13 }
14 int main(){
15     init();
16 }
复制代码

 

 

 

 

 

 

 

 

 

 

 

 

 

 

好戏都是要留到最后的≖‿≖✧确实还有O(n)的做法

这个算法名字叫线筛

 

复制代码
 1 #include<cstdio>
 2 const int N = 100000 + 5;
 3 bool prime[N];//prime[i]表示i是不是质数 
 4 int p[N], tot;//p[N]用来存质数 
 5 void init(){
 6     for(int i = 2; i < N; i ++) prime[i] = true;//初始化为质数 
 7     for(int i = 2; i < N; i++){
 8         if(prime[i]) p[tot ++] = i;//把质数存起来 
 9         for(int j = 0; j < tot && i * p[j] < N; j++){
10             prime[i * p[j]] = false;
11             if(i % p[j] == 0) break;//保证每个合数被它最小的质因数筛去 
12         }
13     }    
14 }
15 int main(){
16     init();
17 }
复制代码

 

 

这个方法可以保证每个合数都被它最小的质因数筛去

所以一个数只会经过一次

时间复杂度为O(n)

 

其实loglogn非常小,把埃筛看成线性也无妨,毕竟它比线筛好写

 

 

 

基于埃筛的原理,我们可以用它干很多事

比如预处理每个数的所有质因数

 

#include<cstdio>
#include<vector>
using namespace std;
const int N = 100000 + 5;
vector<int > prime_factor[N];
void init(){
    for(int i = 2; i < N; i ++){
        if(prime_factor[i].size() == 0){//如果i是质数 
            for(int j = i; j < N; j += i){
                prime_factor[j].push_back(i); 
            }
        }
    }
}
int main(){
    init();
}

 

 

比如预处理每个数的所有因数

 

#include<cstdio>
#include<vector>
using namespace std;
const int N = 100000 + 5;
vector<int > factor[N];
void init(){
    for(int i = 2; i < N; i ++){ 
        for(int j = i; j < N; j += i){
            factor[j].push_back(i); 
        }
    }
}
int main(){
    init();
}

 

 

比如预处理每个数的质因数分解

 

#include<cstdio>
#include<vector>
using namespace std;
const int N = 100000 + 5;
vector<int > prime_factor[N];
void init(){
    int temp;
    for(int i = 2; i < N; i ++){
        if(prime_factor[i].size() == 0){
            for(int j = i; j < N; j += i){
                temp = j;
                while(temp % i == 0){
                    prime_factor[j].push_back(i);
                    temp /= i;
                } 
            }
        }
    }
}
int main(){
    init();
}

 

 

 https://vjudge.net/contest/240113#problem/A

https://vjudge.net/contest/240113#problem/H

#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxm = 1e6 + 5;
bool  is_prime[maxm];
bool  is_prime_small[maxm];
//对区间【a, b)内的整数执行筛选。is_prime[i - a] = true i是素数
void segment_solve(ll a, ll b) { for(int i = 0; (ll) i * i < b; i++) is_prime_small[i] = true; for(int i = 0; i < b - a; i++) is_prime[i] = true; for(int i = 2; (ll) i * i < b; i++) { if(is_prime_small[i]) { for(int j = 2 * i; (ll) j * j < b; j += i) is_prime_small[j] = false;//筛[2, 根号b) for(ll j = max(2LL, (a + i - 1) / i) * i; j < b; j += i) is_prime[j - a] = false;//筛[a, b) } } } int main() { segment_solve(20LL, 100LL); for(int i = 0; i < 80; i++) { if(is_prime[i] == true) printf("%d\n", i + 20); } return 0; }



这个是求区间素数的代码。挑战书上
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long LL;
const int N=1e5+5;

bool vis[N],visab[N];
int prime[N],cnt=0;
void is_prime()
{
    memset(vis,0,sizeof(vis));
    vis[1]=1;
    for(int i=2;i<N;i++)
    {
        if(!vis[i])
        {
            prime[cnt++]=i;
            for(int j=i+i;j<N;j+=i)
                vis[j]=1;
        }
    }
}


int main()
{
    int t;
    cin>>t;
    is_prime();
    for(int kase=1;kase<=t;kase++)
    {
        LL a,b;
        scanf("%lld%lld",&a,&b);
        int count=0;
        if(b<=N-1)
        {
            for(LL i=a;i<=b;i++)
            {
                if(!vis[i])
                    count++;
            }
        }
        else
        {
            memset(visab,0,sizeof(visab));
            for(int i=0;i<cnt&&prime[i] * prime[i]<=b;i++)
            {
                LL k=a/prime[i];
                if(k*prime[i]<a)
                    k++;
                for(LL j=k*prime[i];j<=b;j+=prime[i])
                {
                    visab[j-a]=1;
                }
            }
            for(LL i=a;i<=b;i++)
            {
                if(!visab[i-a])
                    count++;
            }
        }
        printf("Case %d: %d\n",kase,count);
    }
}

 

题目:给定整数a和b;请问区间【a, b)内有多少个素数

a < b <= 1e12;

b - a <= 1e6;

先分别做好2到根号b的表和a到b 的表,然后从2到根号b的表筛的素数的同时,也将其倍数从a到b的表中划去,最后剩下的就是a到b的素数了。

 

 

 

 https://vjudge.net/contest/230809#problem/T

给定一个七位数,然后他的反转数是一个素数,且要小于1e6,说明原七位数最后一位为零。

题目要求满足这些条件的七位数的质因子之和,然后还有可能删除某个数。

所以用两个树状数组,一个记录个数,一个记录值。

#include<bits/stdc++.h>

using namespace std;
typedef long long ll;
const int maxm = 1e6 + 5;
int pri[maxm], is_p[maxm], a[maxm];
int num[10];
ll bit_a[maxm], bit_b[maxm];
ll fac[maxm];
map<int, int> mapp;
int cnt;
ll cal(int x) {
    int ax = x;
    int c = 0;
    while(ax) {
        num[c++] = ax % 10;
        ax /= 10;
    }
    ll res = 0;
    for(int i = 0; i < c; i++) {
        res = res * 10 + num[i];
    }
    while(res < 1e5) res *= 10;
    return res;
}
void init() {
    cnt = 0;
    for(int i = 2; i < 1e6; i++) is_p[i] = 1;
    for(int i = 2; i < 1e6; i++) {
        if(is_p[i]) {
            pri[++cnt] = i;
            for(int j = 2 * i; j < 1e6; j += i) {
                is_p[j] = 0;
            }
        }
    }
    for(int i = 1; i <= cnt; i++) {
         a[i] = cal(pri[i]);
    }
    sort(a + 1, a + cnt + 1);
    for(int i = 1; i <= cnt; i++) {
        mapp[a[i] ] = i;
    }
    for(int i = 1; i <= cnt; i++) {
        int tmp = a[i];
        fac[i] = 2;
        for(int j = 1; j <= cnt && pri[j] * pri[j] <= tmp; j++) {
            while(tmp % pri[j] == 0) {
                tmp /= pri[j];
                fac[i]++;
            }
        }

        if(tmp > 1) fac[i]++;
    }
}

int lowbit(int x) {
    return x & -x;
}

void add(int x, ll val, ll bit[]) {
    while(x <= cnt) {
        bit[x] += val;
        x += lowbit(x);
    }
}

ll sum(int x, ll bit[]) {
    ll s = 0;
    while(x > 0) {
        s += bit[x];
        x -= lowbit(x);
    }
    return s;
}

char ch[5];
int x;
int main() {
init();
memset(bit_a, 0, sizeof(bit_a));
memset(bit_b, 0, sizeof(bit_b));
for(int i = 1; i <= cnt; i++) {
    add(i, 1, bit_a);
    add(i, fac[i], bit_b);
}
while(~scanf("%s", ch)) {
    scanf("%d", &x);
    if(ch[0] == 'q') {
        x++;
        int l = 1, r = cnt, mid, res = cnt + 1;
        while(l <= r) {
            mid = (l + r) >> 1;
            if(sum(mid, bit_a) >= x) {
                res = mid;
                r = mid - 1;
            }
            else {
                l = mid + 1;
            }
        }
        printf("%lld\n", sum(res, bit_b));
    }
    else if(ch[0] == 'd') {
        int pos = mapp[x / 10];
        add(pos, -1, bit_a);
        add(pos, -fac[pos], bit_b);
    }
}
return 0;
}

 

 

 

 

转载于:https://www.cnblogs.com/downrainsun/p/9754288.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值