数论之埃氏筛,线性筛,欧几里得算法,欧拉函数,欧拉定理,费马定理,快速幂,扩展欧几里得算法,线性同余方程,中国剩余定理(acwing模板篇)

目录

质数(素数)

经典问题:给定n个正整数ai,判定每个数是否是质数

(0)暴力遍历(O(N))

(1)试除法判定素数(O(sqrt(N))

经典问题:给出一个数n,求1~n内素数的个数

(1)朴素筛法求素数

(2)线性筛

分解质因数——试除法(O(sqrt(N)*logN))

经典问题:差分数字->质数^指数

约数

(1)试除法求约数

(2)约数个数 

(3)约数之和 

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

欧拉函数

筛法求欧拉函数

 费马定理

 快速幂

快速幂求逆元

 扩展欧几里得算法

 线性同余方程(拓展欧几里得的应用)

中国剩余定理


质数(素数)

经典问题:给定n个正整数ai,判定每个数是否是质数

注意:2是最小的素数(质数),1不是素数(质数)

(0)暴力遍历(O(N))

#include<iostream>
using namespace std;
bool is_prime(int n)
{
	if (n < 2) return false;
	for (int i = 2; i < n; i++)
	{
		if (n % i == 0)
		{
			return false;
		}
	}
	return true;
}
int main()
{
	int n;
	cin >> n;
	while (n--)
	{
		int x;
		cin >> x;
		if (is_prime(x)) puts("Yes");
		else puts("No");
	}

	return 0;
}

(1)试除法判定素数(O(sqrt(N))

思想:如果d是n的因子,那么n/d也是n的因子,故i的枚举范围可以缩小到2~i<=(n/i)

即可写成i<=(n/i)

#include<iostream>
using namespace std;
bool is_prime(int n) 
{
	if (n < 2) return false;
	for (int i = 2; i <= n / i; i++)//为什么这里需要写“=”?
	{
		if (n % i == 0)
			return false;
	}
	return true;
}
int main() 
{
	int n;
	cin >> n;
	while (n--) 
	{
		int x;
		cin >> x;
		if (is_prime(x)) puts("Yes");
		else puts("No");
	}
	return 0;
}

问:为什么is_prime函数的第二个for位置需要写“=”?

答:避免平方数的情况:比如说“4”,如果不写“=”,那么将不会执行for循环的语句,return true

问:为什么is_prime函数的第二和for位置不能写成i*i<=n 或者 i<=sqrt(n)?

答:i*i在i接近int范围时容易溢出,i<=sqrt(n)需要每次计算时调用库函数,降低运行效率


经典问题:给出一个数n,求1~n内素数的个数

(1)朴素筛法求素数

思路:

需要两个数组,一个为primes:存放素数,一个为st数组:判断是否为素数

同样是以i为间隔,标记i的倍数为true,那么就确定了这些i的倍数不是素数

模拟过程:

因为全局变量未初始化均为0,所以st[2]=0,可以通过if判断,并将i=2进入primes数组,然后遍历标记i的倍数,为true,那么相当于第一层for循环遍历到4时,将不会执行if判断,因为st[4]=true;如果一个数,从未被标记过,证明前面的数都不是它的因子证明了它就是质数

#include<iostream>
using namespace std;
const int N = 1000010;
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;
			for (int j = i + i; j <= n; j += i) st[j] = 1;//间隔
		}
	}
}
int main() 
{
	int n;
	cin >> n;
	get_primes(n);
	cout << cnt;
}

(2)线性筛

思路与上面的朴素筛法差不多,都是标记倍数,不过线性筛每次时标记离自己最近那个倍数

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1000010;
int primes[N], cnt;
bool st[N];
void get_primes(int n)//12
{
	for (int i = 2; i <= n; i++) 
	{
		if (!st[i])//如果没有被标记过:证明时素数
		{
			primes[cnt++] = i;//primes[0]=2  primes[1]=3
		}
		for (int j = 0; primes[j] <= n / i; j++)
		{//st[4]=1  st[6]=1 st[9]=1
			st[primes[j] * i] = 1;//遍历素数数组,标记素数的i倍为true
			if (i % primes[j] == 0) break;//2%2==0  3%3==0 意思时遍历到最后primes数组的最后一个数了 break
		}
	}
}
int main() 
{
	int n;
	cin >> n;
	get_primes(n);
	cout << cnt;

	return 0;
}

分解质因数——试除法(O(sqrt(N)*logN))

经典问题:拆分数字->质数^指数

给定n个正整数ai ,将每个数分解质因数,并按照质因数从小到大的顺序输出每个质因数的底数和指数

另外有性质,n中最多只包含一个大于sqrt(n)的质因子,所以我们只需要枚举[ 2 , n ] 质因子,然后特判一下n最后是否大于1就可以了

 模拟过程:

(1)我们假设n=6

(2)此时第一次for循环为:for(i=2;i<=3;i++)(注意此时n的值并未发生改变)

(3)执行if(6%2==0) ->(此时我们只能得出2是它的约数,当然实际上2也是质数)

(4)定义次数变量s,进入while循环,当n不是i的倍数时跳出

······<1>               n=6/2=3         s=0+1

(5)此时跳出 输出质因子2的次数为1  ->也就是6中2只出现1次

经过上次的循环:n=3,那么再进入for循环,i++,所以for(i=3;i<=1;i++)

不执行for循环将最后的因子直接加入        《i=3  s=1》

 核心:本题是用了标记法:对于一个正整数N,如果遍历到了一个它能整除的数,比如说2,那么这个数就会一直除2,直到这个数变为了不能整除2的时候,就跳出循环,此时记录下它的因子和次数,这个操作相当于遍历到2:并将2的所有倍数删除,接着遍历到3,并将3的所有倍数删除,接着遍历到4,此时n一定不能整除4,因为如果它能被4整除,那么它一定能被2整除,所以不执行if(n%i)

这样就保证了每次的i均为质数

#include<iostream>
using namespace std;
void divide(int n) 
{
	for (int i = 2; i <= n / i; i++) 
	{
		if (n % i == 0) 
		{//i一定是质数,因为此时2到i-1的质因子已经被除干净了 
			int s = 0;//计算次数 
			while (n % i == 0) 
			{
				n /= i;//i为什么是质数的原因
				s++;
			}
			printf("%d %d\n", i, s);
		}
	}
	if (n > 1) printf("%d %d\n", n, 1);//特判:当有一个比较大的质因子时
	cout << endl;

}
int main() 
{
	int n;
	cin >> n;
	while (n--) 
	{
		int x;
		cin >> x;
		divide(x);
	}
	return 0;
}

约数

正整数中质数以外的数就是约数了^  ^

(1)试除法求约数

给定n个正整数x,对于每个整数x,请你按照从小到大的顺序输出它的所有约数

注意:提升效率的点:i<=(n/i) ,和push_back(i),且push_back(n/i)   同时操作两个

#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
vector<int> get_divisors(int n) 
{
	vector<int> res;
	for (int i = 1; i <= n / i; i++) 
	{
		if (n % i == 0) 
		{
			res.push_back(i);
			if (i != n / i) res.push_back(n / i);
		}
	}
	sort(res.begin(), res.end());
	return res;
}
int main() 
{
	int n;
	cin >> n;
	while (n--) 
	{
		int x;
		cin >> x;
		auto res = get_divisors(x);
		for (auto i : res) 
		{
			cout << i << " ";
		}
		cout << endl;
	}
	return 0;
}

问:为什么要加上一个if (i != n / i) res.push_back(n / i)?这个if的作用是什么?

答:if是用来判断i*i!=n,这样就清晰很多了,就是判断n为平方数时的情况,会重复添加i元素


(2)约数个数 

 这里需要用到两个公式:

一个数N分解质因数为如下形式:

 约数的个数公式为:

 图片来源:[AcWing]870. 约数个数(C++实现)求约数个数模板题_cloud的博客-CSDN博客_c++求约数个数

 原理在这篇大佬的博客就有

概括一下就是:得到质因数的出现次数

那么对于出现次数而言的范围是0~最高次数(也就是求出的afa)

因为对于每个数而言,既然它都是质因数,那么我可以选0次,也可以选最高次数次

所以是afa+1,再乘以其他数的(次数总和+1)即可

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

int main() 
{
	int n;
	cin >> n;
	ll ans = 1;//因为要连乘,所以初始化为1
	unordered_map<int, int> hash;
	while (n--) 
	{
		int x;
		cin >> x;
		for (int i = 2; i <= x / i; i++) 
		{
			while (x % i == 0) 
			{
				x /= i;//分解质因数
				hash[i]++;//质因数的指数++(次数即指数)
			}
		}
		if (x > 1) hash[x]++;//保留最后一个质因数
	}
	for (auto &i : hash) ans = ans * (i.second + 1) % mod;//约数公式
	cout << ans;
    
    return 0;
}

(3)约数之和 

 公式原理:

因为拆开任何一项,都是一个约数,相加便是约数之和。(这个公式是将所有约数相加后提取公因数得到的

[AcWing]AcWing 871. 约数之和 (C++实现)约数之和模板题_cloud的博客-CSDN博客_约数之和c++

#include <iostream>
#include <algorithm>
#include <unordered_map>

using namespace std;
typedef long long LL;
const int N = 110, mod = 1e9 + 7;

int main()
{
    int n;
    cin >> n;
    unordered_map<int, int> primes;

    while (n--)
    {
        int x;
        cin >> x;

        for (int i = 2; i <= x / i; i++)
            while (x % i == 0)
            {
                x /= i;
                primes[i] ++;
            }

        if (x > 1) primes[x] ++;
    }

    LL res = 1;

    // ------------------约数之和公式-------------------------
    for (auto p : primes)
    {
        LL a = p.first, b = p.second;
        LL t = 1;
        while (b--) t = (t * a + 1) % mod; // while (b -- ) t = (t * a + 1) % mod; 是什么意思?
        res = res * t % mod;
    }

    cout << res << endl;

    return 0;
}

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

[AcWing]872. 最大公约数 (C++实现)最大公约数模板题_cloud的博客-CSDN博客

 核心:gcd(a,b)=gcd(b,a%b)

分两种情况:

(1)当b==0时,0与任意一个数的最大公约数都是它本身

(2)当b!=0时就是gcd(a,b)=gcd(b,a%b)

#include<iostream>
#include<algorithm>
using namespace std;
int gcd(int a, int b)
{
	return b ? gcd(b, a % b) : a;
}
int main()
{
	int n;
	cin >> n;
	while (n--)
	{
		int a, b;
		cin >> a >> b;
		cout << gcd(a, b) << endl;
	}

	return 0;
}

欧拉函数

经典问题:给定n个正整数x,请你求出每个数的欧拉函数

欧拉函数:求出1~x中的与x互质的数的个数

互质:最大公约数为1的两个数:gcd(a,b)==1时,称a,b两数互质

原理:为链接处

[AcWing]873. 欧拉函数(C++实现)欧拉函数模板题_cloud的博客-CSDN博客_c++ 欧拉函数

 

#include<iostream>
#include<algorithm>
using namespace std;

int phi(int x)
{
	int res = x;
	for (int i = 2; i <= x / i; i++)//分解质因数常用模板
	{
		if (x % i == 0)
		{
			res = res / i * (i - 1);//想干的操作:唯一不同的点
			while (x % i == 0) x /= i;
		}
	}
	if (x > 1) res = res / x * (x - 1);

	return res;
}

int main()
{
	int n;
	cin >> n;
	while (n--)
	{
		int x;
		cin >> x;
		cout << phi(x) << endl;
	}

	return 0;
}

筛法求欧拉函数

[AcWing]874. 筛法求欧拉函数(C++实现)线性筛过程中求欧拉函数模板题_cloud的博客-CSDN博客

#include<iostream>
using namespace std;

typedef long long LL;
const int N = 100010;
int primes[N];
int cnt;
int euler[N];//存放每个数i的欧拉数的个数
bool st[N];

void get_eulers(int n)
{
	euler[1] = 1;
	for (int i = 2; i <= n; i++)
	{
		if (!st[i])
		{
			primes[cnt++] = i;//线性筛
			euler[i] = i - 1;//第一种情况:i是n的一个质数:与它互质的数的个数为i-1个
		}
		for (int j = 0; primes[j] <= n / i; j++)//线性筛
		{
			int t = primes[j] * i;
			st[t] = true;//标记
			if (i % primes[j] == 0)
			{//第二种情况:i%pj==0 :pj*i的欧拉数为pj乘上i的欧拉数
				euler[t] = euler[i] * primes[j];
				break;
			}
			euler[t] = euler[i] * (primes[j] - 1);
		}//第三种情况:i%pj!=0  pj*i的欧拉数为i的欧拉数乘以(pj-1)
	}
}

int main()
{
	int n;
	cin >> n;

	get_eulers(n);

	LL res = 0;
	for (int i = 1; i <= n; i++) res += euler[i];

	cout << res << endl;

	return 0;
}

 费马定理


 快速幂

[AcWing]875. 快速幂(C++实现)快速幂模板题_cloud的博客-CSDN博客

#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;


LL qmi(int a, int b, int p)
{
    LL res = 1 % p;
    // 其实就是求k的二进制数,再用这二进制数求出a的k次方模p的结果
    while (b)
    {
        // 如果当前k的末尾是1
        if (b & 1) res = res * a % p;
        // 组合求值
        a = a * (LL)a % p;
        // 向右移一位
        b >>= 1;
    }
    return res;
}


int main()
{
    int n;
    scanf("%d", &n);
    while (n -- )
    {
        int a, b, p;
        scanf("%d%d%d", &a, &b, &p);
        printf("%lld\n", qmi(a, b, p));
    }

    return 0;
}

快速幂求逆元

[AcWing]876. 快速幂求逆元(C++实现)快速幂求逆元模板题_cloud的博客-CSDN博客

#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;


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


int main()
{
    int n;
    scanf("%d", &n);
    while (n -- )
    {
        int a, p;
        scanf("%d%d", &a, &p);
        if (a % p == 0) puts("impossible");
        else printf("%lld\n", qmi(a, p - 2, p));
    }

    return 0;
}

 扩展欧几里得算法

[AcWing]877. 扩展欧几里得算法(C++实现)扩展欧几里得算法模板题_cloud的博客-CSDN博客_扩展欧几里得算法c++

#include<iostream>
#include<algorithm>
using namespace std;

void exgcd(int a, int b, int& x, int& y)
{
	if (!b)
	{
		x = 1, y = 0;
		return;
	}
	exgcd(b, a % b, y, x);
	y -= a / b * x;
	return;
}

int main()
{
	int n;
	cin >> n;

	while (n--)
	{
		int a, b;
		cin >> a >> b;
		int x, y;
		exgcd(a, b, x, y);
		printf("%d %d\n", x, y);
	}

	return 0;
}

 线性同余方程(拓展欧几里得的应用)

(拓展欧几里得+求余)acwing 878. 线性同余方程_岁忧的博客-CSDN博客

#include<iostream>
using namespace std;
int exgcd(int a,int b,int &x,int &y)
{//拓展欧几里得 
	if(!b)
    {
		x=1,y=0;
		return a;
	}
	int d=exgcd(b,a%b,y,x);//注意x和y交换一下位置
	y=y-a/b*x;
	return d;//返回最大公约数 
}
int main()
{
	int n;
	cin>>n;
	while(n--)
    {
		int a,b,x,y;
		cin>>a>>b;
		exgcd(a,b,x,y);//用x,y的引用带回
		printf("%d %d\n",x,y); 
	}
}

中国剩余定理(待回看)

中国剩余定理【数论】_100days-CSDN博客_中国剩余定理

#include<iostream>
using namespace std;
typedef long long ll;
ll exgcd(ll a, ll b, ll& x, ll& y) 
{
	if (!b)
	{
		x = 1, y = 0;
		return a;
	}
	ll d = exgcd(b, a % b, y, x);
	y = y - a / b * x;
	return d;
}
int main()
{
	int n;
	cin >> n;
	ll a1, m1;
	cin >> a1 >> m1;
	bool flag = 1;

	for (int i = 0; i < n - 1; i++)
	{
		ll a2, m2, k1, k2;
		cin >> a2 >> m2;
		ll d = exgcd(a1, -a2, k1, k2);
		if ((m2 - m1) % d) {
			flag = 0;
			break;
		}

		k1 = (k1 * (m2 - m1) / d) % abs(a2 / d);
		m1 = k1 * a1 + m1;
		a1 = abs(a1 / d * a2);//a1*a2/d是最小公倍数
	}
	if (flag) cout << (m1 % a1 + a1) % a1;//取余的技巧,为了得到一个正数
	else cout << -1;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值