1. 主页
  2. 文档
  3. Level3题解(11-20)
  4. 第12课 质数

第12课 质数

质因数分解

  • 1、用试除法分解质因子
  • 2、用Pollard_rho启发式方法分解质因子

每个合数都可以写成几个质数相乘的形式,这几个质数就都叫做这个合数的质因数如果一个质数是某个数的因数,那么就说这个质数是这个数的质因数。而这个因数一定是一个质数。

定义

质因数(或 质因子)在 数论里是指能整除给定正 整数的 质数。 两个没有共同质因子的正整数称为互质。因 为1没有质因子,1与任何正整数(包括1本身)都是互质。 正整数的因数分解可将正整数表示为一连串的质因子相乘,质因子如重复可以指数表示。根据算术基本定理, 任何正整数皆有独一无二的质因子分解式。只有一个质因子的正整数为质数。

例子

  • 1没有质因子。
  • 5只有1个质因子,5本身。(5是质数。)
  • 6的质因子是2和3。(6 = 2 × 3)
  • 2、4、8、16等只有1个质因子:2(2是质数,4 = 2,8 = 2,如此类推。)
  • 10有2个质因子:2和5。(10 = 2 × 5)

就是一个数的 约数,并且是 质数,比如8=2×2×2,2就是8的质因数。12=2×2×3,2和3就是12的质因数。把一个式子以12=2×2×3的形式表示,叫做 分解质因数。16=2×2×2×2,2就是16的质因数, 把一个合数写成几个质数相乘的形式表示,这也是分解质因数。 分解质因数的方法是先用一个合数的最小质因数去除这个合数,得出的数若是一个质数,就写成这个合数相乘形式;若是一个合数就继续按原来的方法,直至最后是一个质数 。分解质因数的有两种表示方法,除了大家最常用知道的“短除分解法”之外,还有一种方法就是“塔形分解法”。分解质因数对解决一些 自然数和 乘积的问题有很大的帮助,同时又为求 最大公约数和 最小公倍数做了重要的铺垫。

试除法

短除法求一个数分解质因数,要从最小的质数除起,一直除到结果为质数为止。分解质因数的算式的叫短除法,和除法的性质差不多,还可以用来求多个个数的公因式:

求 最大公因数的一种方法,也可用来求 最小公倍数。

求几个数最大公因数的方法,开始时用观察比较的方法,即:先把每个数的因数找出来,然后再找出公因数,最后在公因数中找出最大公因数。

 例如:求12与18的最大公因数。

例如:求12与18的最大公因数。
12的因数有:1、2、3、4、6、12。
18的因数有:1、2、3、6、9、18。
12与18的 公因数有:1、2、3、6。
12与18的最大公因数是6。
这种方法对求两个以上数的最大公因数,特别是数目较大的数,显然是不方便的。于是又采用了给每个数分别分解质因数的方法。
12=2×2×3
18=2×3×3
12与18都可以分成几种形式不同的乘积,但分成质因数连乘积就只有以上一种,而且不能再分解了。所分出的质因数无疑都能 整除原数,因此这些质因数也都是原数的约数。从分解的结果看,12与18都有 公约数2和3,而它们的乘积2×3=6,就是 12与18的最大公约数。
采用分解质因数的方法,也是采用短除的形式,只不过是分别短除,然后再找 公约数和最大公约数。如果把这两个数合在一起短除,则更容易找出 公约数和最大公约数。
从短除中不难看出,12与18都有 公约数2和3,它们的乘积2×3=6就是12与18的最大公约数。与前边分别分解质因数相比较,可以发现:不仅结果相同,而且 短除法 竖式左边就是这两个数的公共质因数,而两个数的最大公约数,就是这两个数的公共质因数的连乘积。
实际应用中,是把需要计算的两个或多个数放置在一起,进行短除。
在计算多个数的最小公倍数时,对其中任意两个数存在的约数都要算出,其它无此约数的数则原样落下。最后把所有约数和最终剩下无法 约分的数连乘即得到最小公倍数。
只含有1个质因数的数一定是 亏数。

(短除法详解:短除符号就是除号倒过来。短除就是在除法中写 除数的地方写两个数共有的 质因数,然后落下两个数被公有质因数整除的商,之后再除,以此类推,直到结果 互质为止(两个数互质)。

而在用短除计算多个数时,对其中任意两个数存在的因数都要算出,其它没有这个因数的数则原样落下。直到剩下每两个都是 互质关系。求最大公因数 乘一边,求最小公倍数 乘一圈。

( 公约数:亦称“公 因数”。是几个整数同时均能整除的整数。如果一个整数同时是几个整数的约数,称这个整数为它们的“公约数”;公约数中最大的称为最大公约数。)

在用短除计算多个数时,对其中任意两个数存在的因数都要算出,其它没有这个因数的数则原样落下。直到剩下每两个都是互质关系。 求最大公约数遍乘左边所有数公共的因数,求最小公倍数遍乘一圈。这种方法对求两个以上数的最大公因数,特别是数目较大的数,显然是不方便的。于是又采用了给每个数分别 分解质因数的方法)

int p[20];  //p[]记录因子,p[1]是最小因子。一个int数的质因子最多有10几个
int c[40];  //c[i]记录第i个因子的个数。一个因子的个数最多有30几个

void factorization(int n){
    int m = 0;
    for(int i = 2; i*i <= n; i++)
        if(n%i == 0){
           p[++m] = i, c[m] = 0;
           while(n%i == 0)            //把n中重复的因子去掉
              n/=i, c[m]++;    
        }
    if(n>1)                           //没有被除尽,是素数
       p[++m] = n, c[m] = 1;  
}

Pollard Rho因数分解

1975年,John M. Pollard提出了第二种因数分解的方法,Pollard Rho快速因数分解。该算法时间复杂度为O(n^(1/4))。 分解质因数代码:将一个正整数分解质因数。例如:输入90,打印出90=2*3*3*5。
程序分析:对n进行分解质因数,应先找到一个最小的质数k,然后按下述步骤完成: 
(1)如果这个质数恰等于n,则说明分解质因数的过程已经结束,打印出即可。
(2)如果n<>k,但n能被k整除,则应打印出k的值,并用n除以k的商,作为新的正整数你n,
 重复执行第一步。
(3)如果n不能被k整除,则用k+1作为k的值,重复执行第一步。

//poj 1811题:输入一个整数n,2<=N<2^54,判断它是否为素数,如果不是,输出最小素因子。

typedef long long ll;

ll Gcd (ll a,ll b){  return b? Gcd(b, a%b):a;}

ll pollard_rho (ll n){       //返回一个因子,不一定是素因子
    ll i=1, k=2;
	ll c = rand()%(n-1)+1;
    ll x = rand()%n;
    ll y = x;
    while (true){
        i++;
        x = (mult_mod(x,x,n)+c) % n;   //mult_mod(x,x,n)功能是(x*x) mod n
        ll d = Gcd(y>x?y-x:x-y, n);    //重要:保证gcd的数大于等于0
        if (d!=1 && d!=n) return d;    //算出一个因子 
        if (y==x) return n;            //已经出现过,直接返回
        if (i==k) { y=x; k=k<<1;}
    }
}
void findfac (ll n){                   //找所有的素因子
    if (miller_rabin(n)) {             //用miller_rabin判断是否为素数
        factor[tol++] = n;             //存素因子
        return;
    }
    ll p = n;
    while (p>=n) 
		p = pollard_rho(p);            //找到一个因子
    findfac(p);                        //继续寻找更小的因子
    findfac(n/p);
}

试除法整数分解算法中最简单和最容易理解的算法。但是很低效,有很多更好的分解质因子的方法可以去探索。参考《初等数论及其应用》93页。

Q2.PrimeDistance

求l到r之间相邻的差最小以及最大的质数。

MLE和RE了超级多次,最后发现是一个极其不起眼的东西。。。
这道题l , r≤2^31
,但是r-l ≤≤10^6,所以可以考虑从l,r方面入手。
首先我们知道n的质因子枚举到sqrt{n} 就可以了,那我们可以先将不大于2^{31}次方的质数用线性筛筛出来,
时间复杂度O(\sqrt{r}),约为O(47000)。
然后对于每一组数据,我们先从枚举每个质数,再从l到r枚举,每次直接加prime[i],
然后被枚举到的数就绝对是合数。这里的理论时间复杂度是O((r-l)\sqrt{r}),约为
O(47000000000),完全会T。但是这只是理论,实际来说,当r=2^{31}时,所有枚举的质数
约为5000 50005000个,第二重循坏最多执行500000 500000500000次,最少执行1 11次,
均约为10次!(经程序输出)
那么这一段的时间复杂度就约为O(50000),但是还是有一些卡时。
当然你还可以用goto再简化。在POJ上评测加了goto会少20ms(当然并不建议用goto)。
之后我们就求出了l到r之间的所有质数,那么接下来暴力枚举就可以啦!

#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
#define ll long long
#define M 1000020
#define Inf 1e16
using namespace std;

ll l,r,m,sum,minn,maxn,prime[M],v[M],tail,max_r,max_l,min_r,min_l;
bool ok,p[M];

void find_prime(ll n)  //离线求质数(线性筛)
{
	for (ll i=2;i<=n;i++)
	{
		if (!v[i])
		{
			prime[++m]=i;
			v[i]=i;
		}
		for (ll j=1;j<=m;j++)
		{
			if (prime[j]>v[i]||i*prime[j]>n) break;
			v[i*prime[j]]=prime[j];
		}
	}
}

int main()
{
	find_prime(50000);
	while (~scanf("%lld%lld",&l,&r))
	{
		sum=r-l+1;
		memset(p,0,sizeof(p));
		if (l==1)   //特判,1不是质数
		{
			p[1]=1;
			sum--;
		}
		for (ll i=1;prime[i]<=sqrt(r);i++)
		 for (ll j=l+(prime[i]-(l%prime[i]))%prime[i];j<=r;j+=prime[i])  //直接求出第一个质数,然后就每次加prime[i]
		 {
		 	if (sum<2) goto stop;  //不建议使用
		 	if (j<0||prime[i]==j) continue;
		 	if (!p[j-l+1]) sum--;
		 	p[j-l+1]=true;
		 } 
		stop:
		if (sum<2) 
		{
			printf("There are no adjacent primes.\n");
			continue;
		}
		minn=Inf;
		maxn=-Inf;
		tail=-1;
		for (ll i=l;i<=r;i++)  //爆枚
		 if (!p[i-l+1])
		 {
		 	if (tail>-1)
		 	{
		 		if (i-tail>maxn)
		 		{
		 			maxn=i-tail;
		 			max_l=tail;
		 			max_r=i;
		 		}
		 		if (i-tail<minn)
		 		{
		 			minn=i-tail;
		 			min_l=tail;
		 			min_r=i;
		 		}
		 	}
		 	tail=i;
		 }
		printf("%lld,",min_l);
		printf("%lld are closest, ",min_r);
		printf("%lld,",max_l);
		printf("%lld are most distant.\n",max_r);
	}
	return 0;
}