数论知识点(1)

数论知识点(1)

整除和同余

整除

定义:整数\(n\)除以整数\(d\)的余数为\(0\),则\(d|n\)

同余

\(a,b\)为两个整数,且他们的差\(a-b\)可以被一个自然数\(m\)所整除,则称\(a\)就模\(m\)来说同余,记为:\(a \equiv b(\mod m)\)。它意味着,\(a-b=mk\)

素数

定义:一个数是质数当且仅当只有1和他本身两个因子。
相对应的规定除1以外其他的数就是合数。
几个性质:
1.小于\(n\)的质数约有\(\frac {n}{ln(n)}\)
2.一个合数一定可以分解为若干质数相乘

素性检验

定义:检验一个数是否为素数。

朴素检验方法:

枚举所有不超过\(\sqrt{n}\)的数,对\(n\)进行试除。
这样时间复杂度是\(O(\sqrt{n})\)的。
如果只枚举不超过\(\sqrt{n}\)的质数,时间复杂度就是\(O(\frac{\sqrt{n}}{log~n})\)

\(Miller\)-\(Rabin\)素性检验:

一种基于费马小定理的随机算法,假设需要选取\(k\)个随机种子。
时间复杂度为,\(O(klog^3n)\)
可以用\(FFT\)加速到\(O(klog^2n)\)
模板在\(Pollard-Rho\)中一并给出。

素数筛法

求1~n中的所有素数。

埃式筛(埃拉托色尼筛法)

如果一个数是数,那么这个数任意倍一定不是一个素数。埃式筛法就是基于这个想法产生的。
我们从小到大枚举每一个数,把这个数字的任意倍标记为合数。不难发现我们枚举到的每一个未标记的数一定是质数。

对于每一个素数\(p\),我们都会枚举它的任意倍,假设有 \(n\) 个数,那么对于素数\(p\),我们会枚举\(p,\)\(2p\),\(3p\)...这样的数,一共有\(\frac{n}{p}\)个数,所以时间复杂度就是\(O(\frac{n}{2}+\frac{n}{3}+\frac{n}{5}+...)\)

已知调和级数\(\sum_{k=1}^n\frac{1}{k}\)->\(logn\)。而埃式筛仅仅筛选了其中为素数的部分,素数所占个数大概是\(\frac{1}{ln~n}\)所以实际的时间复杂度远远不足\(nlogn\)。实际上时间复杂度为\(O(nlognlogn)\)

一道模板题

const  int maxn = 1e6+10; 
bitset<maxn> isprime; 
void init(int n){ 
  isprime.set();//清空 
  isprime[0] = isprime[1] = 0; 
  int m = sqrt(maxn+0.5); 
  for(int i = 2;i <= m;i++){ 			
    if(isprime[i]){ 
	for(int j = i*i;j <= m;j+=i) 
	  isprime[j] = 0; 
	} 
    } 
}

欧拉筛

欧拉筛实际上就是埃式筛的一种优化,在埃式筛中我们每找到一个质数就会筛一遍,但是一个合数可能不止一种质因子。于是一个合数就会被筛掉多次,而欧拉筛就是实现了一个合数只被筛一次。

vector<int> prime; 
bitset<maxn> vis; 
void euler(){ 
  vis.reset(); 
  for(int i = 2;i < maxn;i++){ 		
    if(!vis[i]) prime.push_back(i); 
       for(int j = 0;j < prime.size() && i*prime[j] < maxn;j++) { 
	  vis[i * prime[j]] = 1; //找到质数倍  
	  if (i % prime[j] == 0)break; 
       } 
  } 
}

二次筛法
看数据范围做题。如果小的话我们可以直接枚举 \(O(n)\) 即可。但是这里数据范围达到了 \(2^{31}\),即便是欧拉筛也无法使用。

尽管数据范围大到不可做,我们发现区间[ L , R ]的差值不会超过 \(1e6\),这就是破题点。
我们需要改进线性筛法,得到一个可以求得尽管数据很大但是实际区间长度不大的质数筛法,即二次筛法。

在前面已经提到检验一个数\(n\)是否为素数只需要判断前\(\sqrt n\)个数即可,所以实际上判断\(2^{31}\)的数只需要判断不到\(50000\)的数据。
  于是我们得到这样一个 算法,使用欧拉筛筛选1~50000的素数,而后对于每一个给出的区间使用埃式筛筛选素数。

vector<ll> prime;
const ll maxn = 1e6+50;
bitset<maxn> vis;
const ll N = 1e6+50;
bool is_p[N] = {false};
void euler(){
    vis.reset();
    for(ll i = 2;i < maxn;i++){
        if(!vis[i]) prime.push_back(i);
        for(ll j = 0;j < prime.size() && i*prime[j] < maxn;j++) {
            vis[i * prime[j]] = 1;  //找到质数倍
            if (i % prime[j] == 0)break;
        }
    }
}
int main() {
    euler();
    ll l,r;cin>>l>>r;
    if(l == 1)l++;
    for(ll i = 0;prime[i]*prime[i] <= r;i++){
        ll p = prime[i];
        for(ll j = max(2*1ll,(l-1)/p+1)*p;j <= r;j += p){
            if(j >= l)is_p[j-l] = true;
        }
    }
    ll tot = 0;
    for(ll i = l;i<=r;i++) if(!is_p[i-l])++tot;
    cout<<tot<<endl;
    return 0;
}

质因子

关注了素数之后我们一样需要关注合数。
合数主要问题在于因数。
首先给出一个定理:

唯一分解定理(算数基本定理)

除1以外的的所有数都可以被分解为若干质数相乘
\(n = \prod \limits_{i=1}^mp_i^{c^i}\)

证明

朴素质因数分解算法

枚举因子,一个个的除即可,时间复杂度\(O(\sqrt n)\)
一道牛客多校的题目

Pollard-Rho 大质因数分解

P4718 【模板】Pollard-Rho算法
\(Pollard~Rho\)是一个非常玄学的方式,用于在\(O(n^{1/4})\)的期望时间复杂度内计算合数n的某个非平凡因子。事实上算法导论给出的是\(O(\sqrt p)\)\(p\)\(n\)的某个最小因子,满足\(p\)\(n/p\)互质。但是这些都是期望,未必符合实际。但事实上Pollard Rho算法在实际环境中运行的相当不错。

Pollard-Rho的原理就是通过某种方法得到两个整数\(a\)\(b\),而待分解的大整数为\(n\),计算 \(p=gcd(abs(a-b,n))\),直到\(p\)不为1或者\(a,b\)出现循环为止。然后判断\(p\)是否为\(n\),如果\(p=n\)或者\(p=1\)成立,则\(n\)为质数,否则\(p\)\(n\)的一个因数,于是递归的找\(Pollard(n/p)\)
通常使用生成函数\(x[i+1] = (x[i]*x[i]+c) \mod n\)迭代计算\(a\)\(b\),通常取\(c\)为1。当\(a\)\(b\)出现循环时,进行判断。(该生成函数是必然存在循环的)。
现在问题是如何判断循环,这是一种类似与龟兔赛跑的模型,假设有一个环形跑道我们让\(b\)的速度是\(a\)的两倍当\(b\)第一次赶上\(a\)时我们就知道产生了循环。
OIwiki的板子:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <time.h>

#define ll long long
using namespace std;
ll gcd(ll a,ll b){return b?gcd(b,a%b):a;}
ll mul(ll a, ll b, ll p) {//慢速乘似乎过不了
//    ll ans = 0;
//    while (b) {
//        if (b & 1) ans += a, ans %= p;
//        a <<= 1, a %= p;
//        b >>= 1;
//    }
//    return ans%p;
      return (__int128)a*b%p;
}
ll f_pow(ll a,ll b,ll mod){
    ll res = 1;a%=mod;
    while(b){
        if(b&1)res = mul(res,a,mod);
        a = mul(a,a,mod);
        b>>=1;
    }
    return res;
}
bool Miller_Rabin(ll p) {  //判断素数
    if(p < 2) return false;
    if(p == 2 || p == 3) return true;
    ll d = p - 1, r = 0;
    while (!(d & 1)) ++r, d >>= 1;  //将d处理为奇数
    for (ll k = 0; k < 10; ++k) {
        ll a = rand() % (p - 2) + 2;
        ll x = f_pow(a,d,p);
        if(x == 1 || x == p - 1) continue;
        for(ll i = 0;i < r;i++){
            x = mul(x,x,p);
            if(x == p-1)break;
        }
        if(x != p-1) return false;
    }
    return true;
}
ll pollard_rho(ll x) {
    ll s = 0, t = 0;
    ll c = 1ll*rand() % (x - 1) + 1;
    int step, goal;
    ll val = 1;
    for (goal = 1;; goal <<= 1, s = t, val = 1) {//倍增优化
        for (step = 1; step <= goal; ++step) {
            t = (mul(t,t,x)+c)%x;
            val = mul(val , abs(t - s) , x);
            if ((step % 127) == 0) {//似乎是倍增的上线
                ll d = gcd(val, x);
                if (d > 1) return d;
            }
        }
        ll d = gcd(val, x);
        if (d > 1) return d;
    }
}
ll ans = -1;
void find(ll n){
    if(n <= ans || n < 2) return ;
    if(Miller_Rabin(n)){//如果要找所有的质因子,修改这里
        //ans = max(ans,n);
        ans = ans >= n?ans:n;
        //cout<<n<<endl;
        return;
    }
    ll p = n;
    while(p >= n)p = pollard_rho(n);
    while((n%p) == 0)  n/=p;
    find(n),find(p);
}
void solve(ll n){
    find(n);
    if(ans == n)puts("Prime");
    else printf("%lld\n",ans);
}
int main(){
    //ios::sync_with_stdio(false);
    ll t;scanf("%lld",&t);
    while(t--) {
        //srand((unsigned)time(NULL));
        ll n;scanf("%lld",&n);
        ans = -1;
        solve(n);
    }
    return 0;
}

威尔逊定理

\(p\)为素数,则\((p-1)! \equiv -1(\mod p)\)
同时逆定理也成立,即\((p-1)! \equiv -1(\mod p)\),则\(p\)为素数。

费马小定理

\(p\)为素数,\(a\)为正整数,且\(a\)\(p\)互质,则:\(a^{p-1}\equiv 1(\mod p)\)

证明;
\(p-1\)个整数\(a,2a,3a,...,(p-1)a\)中没有一个时\(p\)的倍数。
同时,这\(p-1\)个数不存在两个数同余于模\(p\)
因此他们对\(p\)的同余一定是\(1\)~\((p-1)\)的一种排列(鸽巢原理)。
故,\(a*2a*3a*....*(p-1)a\)\(\equiv\) \(1*2*3*...*(p-1)(\mod p)\)
即,\(a^{p-1}*(p-1)!\equiv(p-1)!(\mod p)\)
根据威尔逊定理\((p-1)!\)\(p\)互质,可得 \(a^{p-1} \equiv 1(\mod p)\)

在一般情况下,还有:
\(p\)为素数且\(a\)是一个正整数,则\(a^p \equiv a(\mod p)\)

欧拉定理

定义欧拉函数\(\varphi(n)\)
表示小于等于\(n\)\(n\)互质的个数。
公式的证明在容斥定理中给出

ll phi[N];
vector<int> prime;
const ll maxn = 1e5;
bitset<maxn> vis;
void euler(){
    vis.reset();
    for(int i = 2;i < maxn;i++){
        if(!vis[i]) prime.push_back(i),phi[i] = i-1;
        for(int j = 0;j < prime.size() && i*prime[j] <= maxn;j++) {
            vis[i * prime[j]] = true; //找到质数倍
            if (i % prime[j] == 0){
                phi[i*prime[j]] = phi[i]*prime[j];
                break;
            }else phi[i*prime[j]]=phi[i]*(prime[j]-1);
        }
    }
}

引理:

  1. 如果\(n\)为一个素数\(p\)\(\varphi(p)\)=\(p-1\)
  2. 如果\(n\)为一个素数\(p^a\),\(\varphi(p)\)=\((p-1)p^{a-1}\)
  3. 如果\(a,b\)互质,那么\(\varphi(ab)\)=\(\varphi(a)\)\(\varphi(b)\)

证明:
1.枚举即可
2.我们可以反过来考虑,所有的可以被\(p\)整除的数,可以看作\(p*t\)\(t\)的最大值就是可以被\(p\)整除的数的个数,可得\(t = p^{a-1}-1\),而比\(p^a\)小的数字有 \(p^a-1\)
3.与\(ab\)互质的数要么与\(a\)互质共\(\varphi(a)\)个,那么与\(b\)互质共\(\varphi(b)\)个,故\(\varphi(ab)\) = \(\varphi(a)\)\(\varphi(b)\)

欧拉定理:若\(a\)\(m\)互质,则\(a^{\varphi(m)}\equiv1(\mod m)\)
P2158

\(gcd\)\(lcm\)

\(gcd(a,b)\)表示\(a,b\)的最大公约数,\(lcm(a,b)\)表示\(a,b\)的最小公倍数

求解\(gcd\)\(lcm\)

不难证明,\(gcd(a,b)*lcm(a,b) == ab\),所以求得\(gcd(a,b)\)即可计算\(lcm(a,b)\)

欧几里得算法

存在性质,\(gcd(a,b)=gcd(b,a\mod b)\)
证明
代码

ll gcd(ll a,ll b){
  return b == 0?a:gcd(b,a%b);
}

更相减损术

存在性质,\(gcd(a,b) = gcd(a,a-b)\)
基于性质可递归写出代码。
时间复杂度不如欧几里得算法,但是该性质是有用的。
这篇题解的题目就用到了这个性质
题解也提到了一些性质,这里就不说了。

特别的
我们将\(gcd(a,b)=1\)时,称\(a,b\)互质。

posted @ 2021-10-14 21:19  Paranoid5  阅读(221)  评论(0编辑  收藏  举报