质数、质因数

质数、质因数

1.判定质数

暴力做法,判断一个数:O(n)

优化暴力做法:只判断一个数的1~根号n O(sqrt(n))

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


bool is_prime(int x)
{
    //1不是质数,也不是合数
    if(x==1) return false;
	//从2开始,1不应该加入
	for (int i = 2;i <= x / i;i++)
	{//i<=n/i 可以防止数据溢出
		if (x % i == 0) return false;
	}
	return true;
}
int main()
{   int q;
	cin >> q;
	while(q--)
	{   
	    int n;
	    cin>>n;
	    if (is_prime(n))
	    {
		    cout << "Yes"<<'\n';
	    }
	    else cout << "No"<<'\n';
    }
    
}

2、分解质因数

基本信息:

质因数(素因数或质因子)在数论里是指能整除给定正整数的质数

概况:算术基本定理:“每一个大于1的整数都能分解成质因数乘积的形式,并且如果把质因数按照由小到大的顺序排列在一起,相同的因数的积写成幂的形式,那么这种分解方法是唯一的。”——又称为“质因数分解定理”

这里有个性质:n中最多只含有一个大于sqrt(n)的因子。证明通过反证法:如果有两个大于sqrt(n)的因子,那么相乘会大于n,矛盾。证毕

于是我们发现最多只有一个大于sqrt(n)的因子,对其进行优化。先考虑比sqrt(n)小的,代码和质数的判定类似
最后如果n还是>1,说明这就是大于sqrt(n)的唯一质因子,输出即可。

证明
1.i为什么一定是质数
首先i是一个最小因子,假设i不是质数,是一个合数,合数必然存在除了1外的其他最小因子,这样子i就不是一个最小因子了,矛盾,证明结束

2.for循环结束,为什么x是一个最大的质数
设for循环结束的x为t

反证法

当t>1时,for循环如果有一个i是大于t的,那么在遇到这个i之前,x会整除t^s,for循环结束后的x就不会是t了,矛盾。证明结束

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

void divide(int x)
{
	for (int i = 2;i <= x / i;i++)
	{
		if (x % i == 0)	//此时i一定是一个质数
		{
			int s = 0;	//表示底数的次方(指数)
			while (x % i == 0)
			{//i可以被整除,指数+1
				x /= i;
				s++;
			}
			cout << i << ' ' << s << ' ' << endl;
			//重新循环,x--->x/(i^s)
		}
	}
	//此时还>1就说明他是最大的质因数
	//因为小的质因数已经被上一步给整除掉了
	if (x > 1)		
	{
		cout << x << ' ' << 1 << endl;
	}
	cout << endl;
}

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

	return 0;

}


3.筛质数

埃氏筛质数法 O(n loglog n)
发现一个质数,就去把 这个质数 作为因子 的所有合数 都标记

void get_primes()
{//找出1~n的所有质数,所以不从1开始,直接从2开始

	for (int i = 2;i <= n;i++)
	{
		if (st[i])	//i是合数 
			continue;
		primes[cnt++] = i;	//i是质数,加入队列
		//这里的j=i*i时间会更快,但是会爆int
		for (int j = i + i;j <= n;j += i)
			st[j] = true;
	}

}

线性筛法(欧拉筛法) O(n)
埃氏筛法的缺陷 :对于一个合数,有可能被筛多次。例如 30 = 2 * 15 = 3 * 10 = 5*6……那么如何确保每个合数只被筛选一次呢?我们只要用它的最小质因子来筛选即可,这便是欧拉筛法。

欧拉筛法:在埃氏筛选法的基础上,只让一个数的最小质因子去染色他,避免重复

两种情况
1.i%primes[j]==0 说明pj是i的最小质因子,同时也是i*pj的最小质因子(染色)

  • 由于我们要确保一个合数只会被一个最小质因子染色,所以如果i%pj==0
    说明下一个比pj大的质数必然不是最小质因子,没有意义应该break

(下一个pj’*i仍然是以i为约数,可知i能被当前pj染色,下一个pj’不是pj‘i的最小质因子,pj是)
2.i%primes[j]!=0 说明pj不是i的最小质因子,应该继续往后找到i的最小质因子
但是pj是i
pj的最小质因子,所以也要染色(因为i从2开始,这样子确保了所有以pj为最小质因子的数都会被染色)

问题
1.为什么某个较大质数,例如5,不用让他去染色 52,53…呢

我们发现,如果一个数t再去染色 tj (j=0…t-1)的数,那么t必然不是最小质因子,一个数以t为最小质因子的话,t对应的约数一定≥t ,所以只有一个数被放入质数队列中,当前的i>=t,我们才需要去染色 it

例如 25=55 49=77…

还有2^n次方 3^n 都会被 i 遍历到

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e6 + 6;
int n;
int cnt;			//记录当前有几个质数
bool st[N];			//记录是否是质数
int primes[N];		//记录质数

void get_primes()
{//找出1~n的所有质数,所以不从1开始,直接从2开始
	for (int i = 2;i <= n;i++)
	{
		if (!st[i]) //是质数,加入
			primes[cnt++] =i;
		//这里和埃氏筛法不同的地方在于,即使是一个合数,也会被遍历
		//这样子确保了2*2,2*3,2*4...都会被筛选到
		for (int j = 0;primes[j] <= n / i;j++)//变形得到primes[j]*i<=n  不去筛选大于n的数
		{
			st[i * primes[j]] = true;
			if (i % primes[j] == 0)	//pj是i的最小质因子 
				break;
		}
	}
}

int main()
{
	cin >> n;
	get_primes();
	cout << cnt;
}

之前还看到一种也很快的

#include <iostream>
#include <cmath>   //要用平方根函数
using namespace std;
#define ll long long   //处理较大的数
bool panduan(ll a) {
	if(a==1)
	return false;     //1既不是质数也不是合数
	if(a==2||a==3)    //5以下得数特判,2,3,都是质数
	return true;
	if(a%6!=5&&a%6!=1)  //5以上的数,判断是不是在6周围,如果不是,肯定是合数
	return false;
	for(int i=5;i<=sqrt(a);i+=6){  //剩下的6周围的数进行判断能不能整除
		if(a%i==0||a%(i+2)==0)
		return false;
	}
	return true;
}

这里附一道题,是关于质因数的。2022ccpc高职组

J 密码

爱丽丝发现了一个宝箱,但是打开宝箱需要破解若干组密码。

每组密码都是一对数字(p,q),同时宝箱会给出两个数字A,B作为提示,爱丽丝需要找到所有满足以下条件的(p,q)。

  • p,q均为质数。
  • A×p+B×qp×q的倍数。

由于满足条件的解有很多,你只需要算出有多少对满足条件的(p,q)。

输入格式:

第一行输入1个整数T(1≤T≤1000)表示询问个数。

接下来T行,每行输入两个整数A,B(1≤A,B≤1012)表示一个询问。

输出格式:

对于每个询问输出一行一个整数,表示答案。

输入样例:

3
2 3
3 7
10 8

输出样例:

2
3
3

A=2,B=3时,(p,q)可能的两个解是(3,2)和(5,5).

A的质因数,B的质因数的组合,加上A+B的质因数的组合种类。

#include <bits/stdc++.h>

#define x first
#define y second
#define ll long long
#define PII pair<int, int>
using namespace std;

bool vis[10000010];
int pri[10000010];
int idx = 0;

void solve() {
    ll a, b, c;
    cin >> a >> b;
    c = a + b;
    vector<int> st;
    for (int i = 0; pri[i] <= c / pri[i]; i++) {//st存储a+b的质因数 
        if (c % pri[i] == 0) {
            st.push_back(pri[i]);
        }

        while (c % pri[i] == 0) {
            c /= pri[i];
        }
    }
    if (c > 1) {
        if (c >= 1000000000)
            st.push_back(-1);
        else
            st.push_back(c);
    }

    set<int> aa;
    for (int i = 0; pri[i] <= a / pri[i]; i++) {//aa存储a的质因数 
        if (a % pri[i] == 0) {
            aa.insert(pri[i]);
        }
        while (a % pri[i] == 0) {
            a /= pri[i];
        }
    }
    if (a > 1) {
        if (c >= 1000000000) {
            if (a == c)
                aa.insert(-1);
            else
                aa.insert(-2);
        } else
            aa.insert(a);
    }

    set<int> bb;
    for (int i = 0; pri[i] <= b / pri[i]; i++) {
        if (b % pri[i] == 0) {
            bb.insert(pri[i]);
        }

        while (b % pri[i] == 0) {
            b /= pri[i];
        }
    }
    if (b > 1) {
        if (c >= 1000000000) {
            if (b == c)
                bb.insert(-1);
            else if (a == b)
                bb.insert(-2);
            else
                bb.insert(-3);
        } else
            bb.insert(b);
    }

    ll ans = aa.size() * bb.size() + st.size();
    for (int i = 0; i < st.size(); i++) {
        if (aa.count(st[i]) && bb.count(st[i]))
            ans--;
    }
    cout << ans << "\n";
}

int main() {
    set<PII > st;
    for (int i = 2; i <= 10000000; i++) {//欧拉筛,筛1~10000000内的质数 
        if (!vis[i])//是质数加入 
            pri[idx++] = i;

        for (int j = 0; pri[j] <= 10000000 / i; j++) {
            //变形得到primes[j]*i<=n  不去筛选大于n的数 
            vis[i * pri[j]] = true;
            if (i % pri[j] == 0)pj是i的最小质因子 
                break;
        }
    }
    int t = 1;
    cin >> t;
    while (t--) {

        solve();
    }
    return 0;
}

引用原文链接:https://blog.csdn.net/qq12323qweeqwe/article/details/121982783

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值