The Festival of Insignificance🍺.|

hsy2093

园龄:1年4个月粉丝:0关注:0

学习笔记——数论

写在前面...

写完了数论的笔记,新知识不确定有没有学懂,但是我的md数学公式写法得到了极大的提升orz

1. 埃式筛法

质数的定义:

针对从2开始的整数定义,如果只包含1和本身这两个因数,则称该数为质数(素数)

(1)质数的判定:试除法

枚举因数的时候,只枚举到因数比较小的那个范围(根号n)

(2)分解质因数:试除法

从小到大枚举所有的数,对于质因子,可通过循环求出每个质因子的个数,从小往大除可以保证其因子一定是质数。

n中最多只包含一个大于sqrt(n)的质因子

朴素筛法时间复杂度为O(nlogn)

定义及其原理

每个合数都可以表示为多个质数的积,因此从最小的质数2开始,用每一个已找到的质数去筛选比它大的数,从而筛掉合数,剩下的就是素数。

算法过程

1)从最小的质数2开始,遍历n以内的所有数i

2)对于当前数i, 判断其是否是质数,如果是,则用i筛掉其在范围内的所有倍数,如i=3时,筛掉n以内的所有3的倍数。

3)不断重复过程2,直到遍历到n

时间复杂度O(nloglogn)

2. 线性筛法

原理及算法过程

n只会被最小质因子筛掉,且合数一定会被筛掉

1) i % prime[j]==0时:

prime[j]一定是i的最小质因子,且prime[j]一定是prime[j]i的最小质因子

2) i % prime[j]!=0时:

prime[j]一定小于i的所有质因子,且prime[j]也一定是prime[j]i的最小质因子

const int N = 10000010;
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;
   	//j从小到大枚举所有质数
   	for(int j = 0; primes[j] <= n / i; j++){
   		st[primes[j] * i] = true;
   		//当该条件成立的时候, prime[j]一定是i的最小质因子
   		if(i % primes[j] == 0)	break;
   	}
   }
}

质数定理

1n中,有n/logn个质数

3. 约数

1)试除法求一个数的所有约数

从小到大枚举所有可能的数字,时间复杂度为O(logn)

2) 求约数个数

N=p1α1p2α2...pkαk

对于一个数的所有约数,一定可以由上面的通式来表示,且有所对应的α1α2α3

约数用M表示,那么M=p1β1p2β2...pkβk

对于β1β2β3的取值范围,为0 ~αi,就能够表示出来所有的约数。所以求约数个数实际可拆解为组合数学(乘法原理)。

所以对于N而言,其对应的约数个数为(α1+1)(α2+1)(α3+1)...(αk+1)

计算时间复杂度

对于i而言,n以内所有倍数的个数表示为n/i

所以n以内所有数的倍数个数表示为i=1i=nn/i

3)求约数之和(没懂)

根据乘法原理和加法原理,约数之和可以表示为:

(p10+p11+...+p1α1)(p20+p21+...+p2α2)...(pk0+pk1+...+pkαk)

#include<stdio.h>
#include<iostream>
#include<unordered_map>
using namespace std;
typedef long long ll;
//unordered_map是个好东西
unordered_map<ll, ll> primes;
ll MOD = 1e9+7;

void cnt_y(ll x){
    for (int i = 2; i <= x / i; i ++ )
        while (x % i == 0){
            x /= i;
            primes[i] ++;
        }
        //如果有大于根号x的质因子
        if (x > 1) primes[x] ++;
}

int main(){
    ll n, a, ans = 1;
    cin >> n;
    for(ll i = 1; i <= n; i++){
        cin >> a;
        cnt_y(a);
    }
    for(auto p : primes){
        ll t = 1;
        //a是质因子值,b是质因子对应的指数
        ll a = p.first, b = p.second;
        for(ll j = 1; j <= b; j++)
            t = (t * a + 1) % MOD;
        ans = ans * t % MOD;
    }
    cout << ans << endl;
    return 0;
}

4)欧几里得算法(辗转相除法)

求两数的最大公约数

通过原理:如果a能整除d,且b能整除d,那么ax+by也能够整除d,可推:

gcd(a,b)=gcd(b,acb)

int gcd(int a, int b){
	return b ? gcd(b, a % b) : a;
}

4. 欧拉函数

ϕ(n)表示为1到n中和n互质的数的个数

p1p2...pn表示n的所有质因子

n=p1α1p2α2...pkαk

那么ϕ(n)=n11p111p2...11pk

其时间复杂度为O(n)

该公式的证明需要用到容斥原理

方法:

从1~n中删掉所有p1p2...pn的倍数,那么剩下的数的个数就可以表示成np1np2n...pkn,但是这样会重复删除掉某些倍数

加上所有pipj的倍数的个数,因为这些数被删掉了两次,其个数可以表示为pipjn

减去所有pipjpk的个数,其个数表示为pipjpkn

一直重复上述过程,偶数因子数要加上,奇数因子数要减掉,直到覆盖掉所有的因子数。这个公式最后就可以由上面ϕ(n)的式子表示

代码实现

int n;
cin >> n;
while(n--){
	int a;
	cin >> a;
	for(int i = 2; i <= a / i; i++){
		if(a % i == 0){
			res = res / i * (i - 1);
			while(a % i == 0)	a %= i;
		}
	}
	//最后如果还有剩 
	if(a > 1)	 res = res / a * (a - 1);
	cout << res << endl;
}

筛法计算欧拉函数

可以通过线性筛来算某一个数的所有质数,从而提高求欧拉函数的效率。

ll get_eulers(int n){
	phi[1] = 1;
	for(int i = 2; i <= n; i++){
		//如果i到当前还没有被筛掉的话,则证明i是一个质数
		if(!st[i]){
			primes[cnt ++] = i;
			//1~i-1以内与质数i互质的个数有
			phi[i] = i - 1;
		}
		//把所有i的倍数都筛掉
		for(int j = 0; primes[j] <= n / i; j++){
			st[primes[j] * i] = 1;
			if(i % primes[j] == 0){
				//公式推导,求i*prime[j]的质因子个数
				phi[i * primes[j]] = phi[i] * primes[j];
				break;
			}
			phi[i * primes[j]] = phi[i] * (primes[j] - 1);
		}
	}
	ll res = 0;
	for(int i = 1; i <= n; i++)		res += phi[i];
	return res;
}

欧拉定理

如果an互质,那么aϕ(n)1(modn)

费马小定理

如果n是一个质数,且an互质,那么an11(modn)

5. 快速幂

快速幂:快速求解ak次方模p的结果,其时间复杂度为O(logk)

其核心思路是预处理出来a20a22...a2k的模p下的值

ak = a2i * a2j * a2k... = a2i+2j+2k+...

该式子即把k以二进制的形式表示。

int quick_mi(int a, int k, int p){
	int res = 1;
	while(k){
		if(k & 1)	res = (ll)res * a % p;
		k >>= 1;
		a = (ll)a * a % p;
	}
	return res;
}

快速幂求逆元

逆元:如果a能够整除b,希望找到一个数,实现abax(modp)

这个数x即叫做bp的逆元,记做b1,所以abab1(modp)

左右两边把a删掉,同乘一个b,也就是bb11(modp)

由费马小定理可得到,如果p是质数,且bp互质,那么bp11(modp),得到bbp21(modp)

对比逆元的公式,如果b是质数,那么b的逆元即等于bp2,即可以利用快速幂求解bp2

int res = quick_mi(a, p-2, p);
//如果a和p互质,那么a的p-2次方即为逆元
if(a % p != 0)		printf("%d\n", res);
//如果p是a的因子,那么对于a而言,不存在逆元
else		print("impossible");

6. 扩展欧几里得算法

裴蜀定理

对于任意一对正整数ab,一定存在非零整数xy,使得ax+by=gcd(a,b)

利用扩展欧几里得算法来求其系数xy

int exgcd(int a, int b, int &x, int &y){    
	//b==0时找到结果
	if(!b){
		x = 1, y = 0;
		return a;
	}
	int d = exgcd(b, a % b, y, x);
	//公式推导
	y -= a / b * x;
    return d;
}

int main(){
	int a, b, x, y;
	scanf("%d %d %d %d", &a, &b, &x, &y);
	exgcd(a, b, x, y);
}

公式推导

边界条件:

b==0时,gcd(a,0)=a

又因为ax+by=gcd(a,b)

所以ax+by=ax=1y=0

递归情况:

ax+by=gcd(a,b)=gcd(b,a mod b)

交换x、y,把ab代替,b用 a mod b 代替,dgcd(a,b),那么

by+(a mod b)x=d

by+(aabb)x=d

ax+(yabx)b=d

所以递归后的系数,x保持不变,y变成yabx

本文作者:hsy2093

本文链接:https://www.cnblogs.com/hsy2093/p/18277715

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   hsy2093  阅读(15)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起