基本数论(上)

一.质数

筛质数:

埃氏筛法:复杂度O(nlogn);

基于的定理是:唯一分解定理:每个数可以分解为唯一的一组质数相乘;

对于每个质数属于[1,n]的倍数部分都先筛出,因为质数的倍数必然不是质数。

可以想一下:假设一个数x不是质数,那么他必有一个质因子y小于x,那么x必将会在循环到y的时候筛除。

优化:我们可以知道我们一定是重复筛除了一些数,事实上我们可以这么做,当我们循环到i时,筛除小于i的质数的i倍的数,但是这样好像没有优化,不妨我们考虑一下,一个数可以被分解为多个质数相乘,那么当i为某个质数z的时候倍数时,那么任何质数的i倍必然会被z筛除。这时候我们可以break,让后面z来筛出。这就是各司其职。

补充:假设某个数m,有质数x,y(x<y),假设m被y所筛出,那么m/y<m/x,那么说m/y必然被x整除,那也就是说从小到大而言,当i=m/y时,必然不会轮到y。所以m不被y所筛出。

题目链接:活动 - AcWing

代码实现:

#include<iostream>
using namespace std;
const int N=1000100;

int primes[N],cnt;
bool st[N];
//效筛法率较低的
/*void get_primes(int n)
{
    for(int i=2; i<=n; i++)
    {
        if(st[i]) continue;
        primes[cnt++]=i;
        for(int j=2; i*j<=n; j++ )
        {
            st[j*i]=true;
        }
    }
    cout<<cnt<<endl;
}*/
//稍微快一点的筛法(线性筛法)
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;//我们将质数的i倍筛出,由于i的质数倍一定比i大,因此,后面的i一定不是质数
            if(i%primes[j]==0) break;//如果i不是质数,他一定在前面或者后面就被筛了,因为任何质数的i倍都是,某一个质数的x倍(这个一定会被筛掉)
        }
    }
    cout<<cnt<<endl;
}
int main()
{
    int n;
    cin>>n;
    get_primes(n);
}

求某个数的质数:采用试除法

时间复杂度O(n\sqrt{m}

题目链接:找不到页面 - AcWing

直接放代码:

#include<iostream>
using namespace std;

void is_prime(int n)
{
    for(int i=2; i<=n/i; i++)
    {
        if(n%i==0)//i一定是质因数
        {
            int cnt=0;
            while(n%i==0)
            {
                cnt++;;
                n/=i;
            }
            cout<<i<<' '<<cnt<<endl;
        }
    }
    if(n!=1) cout<<n<<' '<<1<<endl;
    cout<<endl;
}
int main()
{
    int n;
    scanf("%d",&n);
    while(n--)
    {
        int x;
        scanf("%d",&x);
        is_prime(x);
    }
}

二.约数

下面三个定理记住即可

约数个数:把一个数N 写成:N = (p1^x1^)(p2^x2)(p3^x3)…(pk^xk),其中pi为质数。则N的约数个数为:(x1+1)(x2+1)(x3+1)…(xk+1)

约数和:(p1^{0}+p12+…+p1x1)∗…∗(pk0+pk1+…+pkxk)

最大公约数:辗转相除法:gcd(a,b)=gcd(b,a%b),当某一个为0时停止;

三.欧拉函数

欧拉函数定义:对于一个数n,欧拉函数φ(n)为小于等于n的数中与n互质的个数。

先给公式再证明:  

p为n的分解质数证明如下:

首先对于每个质数,我们可以筛除n/pi的个数,即n-n/pi,但是这样会多删除一部分,比如n/(pi*pj)的部分,所以有需要加上一部分。如此反复循环。然后整合我们可以得到上述的公式(有点像概率论里面的一个公式)

欧拉函数的递推应用:使用到的是欧拉函数是积性函数

给定一个正整数 n,求 1∼n 中每个数的欧拉函数之和。

输入格式

共一行,包含一个整数 n。

输出格式

共一行,包含一个整数,表示 1∼n 中每个数的欧拉函数之和。

我们可以做那么一个推导:从小到大循环1-n,对于每个数,如果这个数为质数,那么他的欧拉函数为i-1,同时把这个数加入primes。接下来我们按照优化后的筛质数步骤来进行,遍历每个已有的质数,对于每个质数的i的倍数,我们可以知道优化后的倍数只执行一次(只是被最小质因子筛),那么对于i*primes来说,i%primes==0,那么意味着primes出现过,于是ans[i*primes]=primes*ans[i]   (实际上是对i*primes的补充);如果i与primes互质,那么可以直接用积性函数进行计算。那么如何保证不重不漏的计算了所有的贡献呢?注意我们代码的逻辑,每个数只被最小质因子筛一次。那么也就是说对于某个数m=p1*p1*p2*p3(p1出现两次);那么m的欧拉函数应该为p1*p1*p2*p3*(1-1/p1)*(1-1/p2)*(1-1/p3),转化一下就变为p1*p1(1-1/p1)*p2*(1-1/p2)*p3*(1-1/p3),实际上就是m=p1*(m/p1的欧拉函数);所以可以知道算法一定是不重不漏的计算了所有的值

代码:

#include<iostream>
#include<unordered_map>
#include<vector>
using namespace std;
const int mod=1e9+7;
typedef long long LL;
int main(){
    int n;
    cin>>n;
    //int res=1;
    vector<LL>ans(n+1,0);
    ans[1]=1;

    vector<int>primes;
    vector<bool>st(n+1,false);
    for(int i=2; i<=n; i++){
        if(!st[i]){
            primes.push_back(i);
            ans[i]=i-1;
        }
        for(auto v:primes){
            if(v*i>n)break;
            st[v*i]=true;
            if(i%v==0){

                ans[i*v]=v*ans[i];
                break;
            }
            ans[i*v]=(v-1)*ans[i];
        }
    }
    LL res=0;
    for(int i=1; i<=n; i++)res+=ans[i];
    cout<<res<<endl;

}

四.快速幂

快速幂:实际上是二进制拆分,将指数拆分为二进制数,对于该位上为1的数,进行相乘;

代码:注意开LL

#include<iostream>
#include<unordered_map>
#include<vector>
using namespace std;
const int mod=1e9+7;
typedef long long LL;

LL qmi(LL a, LL b, int c){
    LL res=1;
    while(b){
        if(b&1){
            //cout<<b<<' '<<a<<endl;
            res*=a;
            res%=c;
        }
        a=a*a%c;
        b>>=1;
        //cout<<b<<endl;
    }
    return res;
}
int main(){
    int n;
    cin>>n;
    while(n--){
        int a,b,p;
        cin>>a>>b>>p;
        cout<<qmi(a,b,p)<<endl;
    }

}

快速幂求逆元:

题目信息:

前置知识:

同余符号:如果a%p==b%p,那么a和b%p同余;

b|a,即a被b整除

证明:

如果b|a 同时(a/b)%m=(a*x)%m

由于取模的运算特性可得:(1/b)%m=x%m

得  1%m=b*x%m

又因为 1%m=b^(m-1)

所以x=b^(m-2)%m;

代码如下:

#include<iostream>

using namespace std;

const int N=1e5+10;
typedef long long LL;


LL qmi(LL a, LL  b, LL p){
    LL res=1;
    while(b){
        if(b&1){
            res=res*a%p;
        }
        a=a*a%p;
        b>>=1;
    }
    return res;
}

int gcd(int a, int b){
    return  b?gcd(b,a%b):a;
}
int main(){
    int n;
    cin>>n;
    for(int i=0; i<n; i++){
        int a,p;
        cin>>a>>p;
        if(gcd(a,p)==1){
            cout<<qmi(a,p-2,p)<<endl;
        }else cout<<"impossible"<<endl;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值