笔者曾获得 ICPC 2020 世界总决赛资格,ICPC 2020 亚洲区域总决赛第五名。
我们将从最简单、最基础的数论开始,由浅入深,逐步介绍在 ACM 算法竞赛以及密码学中常用的数论知识。
自然数是整数的一部分,最简单的数学模型。
Peano 自然数公理
如果有一些对象(可数集),除了它们的数目之外其它性质我们不予考虑的话,我们就可以用自然数来数它们。
若整数
除以非零整数
,商为整数,且余数为零,我们就说
能被
整除(或说
能整除
),
为被除数,
为除数,即
( “
”是整除符号)。
注意
是任何非零整数的倍数。
又称质数,指在大于
的自然数中,除了
和该数自身外,无法被其他自然数整除的数(也可定义为只有
与该数本身两个正因数的数)。大于
的自然数若不是素数,则称之为合数(也称为合成数)。
,即小于等于
的质数大约有
个。
,其中
为质数。
欧拉函数是小于或等于
的正整数中与
互质的数的数目,用
表示。
其中
有通项公式
其中
为
的所有质因数。
利用通项公式,我们可以直接求解欧拉函数的值:
#define LL long long
LL phi(LL x)
{
LL res=x;
for(LL i=2;i*i<=x;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;
}
如果
,则
;
如果
,则
。
https://www.lydsy.com/JudgeOnline/problem.php?id=3884
BZOJ3884 上帝与集合的正确用法
求
的结果。
利用拓展欧拉定理:
,可以得到:
;而
。
显然这是一个不断变成
子问题的过程。
经过不断的
迭代,最终一定会变成
,而显然,
,此时可以开始回溯。考虑需要进行多少次迭代。
是奇数,那么
一定为偶数;
是偶数,那么
一定满足
。
于是可以得到
,所以递归次数级别是
的。时间复杂度为
。
#include<bits/stdc++.h>
#define LL long long
using namespace std;
#define inf 0x3fffffff
LL phi(LL x)
{
LL res=x;
for(LL i=2;i*i<=x;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;
}
LL bin(LL x,LL n,LL MOD)
{
LL ret=MOD!=1;
for (x%=MOD;n;n>>=1,x=x*x%MOD)
if (n&1) ret=ret*x%MOD;
return ret;
}
int T;
LL p;
LL solve(LL x)
{
if (x==1) return 0;
LL p=phi(x);
return bin(2LL,solve(p)+p,x);
}
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%lld",&p);
printf("%lld\n",solve(p));
}
return 0;
}
令
表示
的素数个数。
最前面提到了,素数个数大约是
级别的。
可以用欧拉筛在
的时间复杂度内求得
。
考虑更优的做法。
我们用
表示
的正整数中因子不含有
的个数,其中
是按照顺序从小到大排列的质数。
容易发现
,即去掉能被
整除但是不能被
整数的部分。而且
;当
的时候,
(因为只包含
) 本身。
这样来做,可以保证时间复杂度是
。
从
开始尝试是否是
的约数,直到
。
bool isPrime(LL p){
for (LL i=2;i*i<=p;i++)
if (p%i==0) return false;
return true;
}
根据费马小定理,
,其中
是质数,
是整数。
于是我们可以考虑随机一个
,计算
是否为
,如果不为
,那么肯定不是质数,否则需要继续尝试数次。
如果
,则有
,也就是说所有的
都满足
。
于是可以得到,对于一个合数而言,我们找到一个
使得
的概率是小于
,这样的单次检验正确率已经比较可观了。
不过费马质数判定法有一个致命的弱点,即 Carmichael number 如果用费马质数判定法来检验的话,会被误判。
基于 Fermat 质数检验法的改进。
Fermat 质数检验法,每次都是检验
是否成立,现在假设
。
我们考虑先检验
,然后不断翻倍验证。
因为对于一个质数
有
。于是有从
开始往上倍增的时候一定一直满足
为
。
可以证明检验的单次正确率大于
。证明太复杂,省略了。
对于不同范围数的 MiLLer-Rabin 检验可以使用不同的
,具体是:
内
内
const LL m=7, aa[m]={2, 3, 5, 7, 11, 13, 17};
LL n, p;
LL fmul(LL a, LL b, LL p){ //将b分解为二进制,返回a*b%p
LL ans=0;
for (; b; b>>=1, a+=a, a%=p)
if (b&1) ans+=a, ans%=p;
return ans;
}
LL fpow(LL a, LL x, LL p){
LL ans=1, base=a;
for (; x; x>>=1, base=fmul(base, base, p))
if (x&1) ans=fmul(ans, base, p);
return ans;
}
bool MR(LL a, LL x, LL p){
LL t=fpow(a, x, p);
if (t!=1&&t!=p-1) return false;
if (t==1&&x&1||t==p-1) return true;
return MR(a, x>>1, p);
}
bool isPrime(LL p){
if (p < 2) return false;
if (p == 2) return true;
if (p&1==0) return false;
for (LL i=0; i<m; ++i){
if (p==aa[i]) return true;
if (fpow(aa[i], p-1, p)!=1) return false;
if (!MR(aa[i], (p-1)>>1, p)) return false;
}
return true;
}