数论知识点(1)
数论知识点(1)
整除和同余
整除
定义:整数\(n\)除以整数\(d\)的余数为\(0\),则\(d|n\)。
同余
若\(a,b\)为两个整数,且他们的差\(a-b\)可以被一个自然数\(m\)所整除,则称\(a\)就模\(m\)来说同余,记为:\(a \equiv b(\mod m)\)。它意味着,\(a-b=mk\)。
素数
定义:一个数是质数当且仅当只有1和他本身两个因子。
相对应的规定除1以外其他的数就是合数。
几个性质:
1.小于\(n\)的质数约有\(\frac {n}{ln(n)}\)
2.一个合数一定可以分解为若干质数相乘
素性检验
定义:检验一个数是否为素数。
朴素检验方法:
枚举所有不超过\(\sqrt{n}\)的数,对\(n\)进行试除。
这样时间复杂度是\(O(\sqrt{n})\)的。
如果只枚举不超过\(\sqrt{n}\)的质数,时间复杂度就是\(O(\frac{\sqrt{n}}{log~n})\)。
\(Miller\)-\(Rabin\)素性检验:
一种基于费马小定理的随机算法,假设需要选取\(k\)个随机种子。
时间复杂度为,\(O(klog^3n)\)
可以用\(FFT\)加速到\(O(klog^2n)\)
模板在\(Pollard-Rho\)中一并给出。
素数筛法
求1~n中的所有素数。
埃式筛(埃拉托色尼筛法)
如果一个数是数,那么这个数任意倍一定不是一个素数。埃式筛法就是基于这个想法产生的。
我们从小到大枚举每一个数,把这个数字的任意倍标记为合数。不难发现我们枚举到的每一个未标记的数一定是质数。
对于每一个素数\(p\),我们都会枚举它的任意倍,假设有 \(n\) 个数,那么对于素数\(p\),我们会枚举\(p,\)\(2p\),\(3p\)...这样的数,一共有\(\frac{n}{p}\)个数,所以时间复杂度就是\(O(\frac{n}{2}+\frac{n}{3}+\frac{n}{5}+...)\)。
已知调和级数\(\sum_{k=1}^n\frac{1}{k}\)->\(logn\)。而埃式筛仅仅筛选了其中为素数的部分,素数所占个数大概是\(\frac{1}{ln~n}\)所以实际的时间复杂度远远不足\(nlogn\)。实际上时间复杂度为\(O(nlognlogn)\)。
const int maxn = 1e6+10;
bitset<maxn> isprime;
void init(int n){
isprime.set();//清空
isprime[0] = isprime[1] = 0;
int m = sqrt(maxn+0.5);
for(int i = 2;i <= m;i++){
if(isprime[i]){
for(int j = i*i;j <= m;j+=i)
isprime[j] = 0;
}
}
}
欧拉筛
欧拉筛实际上就是埃式筛的一种优化,在埃式筛中我们每找到一个质数就会筛一遍,但是一个合数可能不止一种质因子。于是一个合数就会被筛掉多次,而欧拉筛就是实现了一个合数只被筛一次。
vector<int> prime;
bitset<maxn> vis;
void euler(){
vis.reset();
for(int i = 2;i < maxn;i++){
if(!vis[i]) prime.push_back(i);
for(int j = 0;j < prime.size() && i*prime[j] < maxn;j++) {
vis[i * prime[j]] = 1; //找到质数倍
if (i % prime[j] == 0)break;
}
}
}
二次筛法
看数据范围做题。如果小的话我们可以直接枚举 \(O(n)\) 即可。但是这里数据范围达到了 \(2^{31}\),即便是欧拉筛也无法使用。
尽管数据范围大到不可做,我们发现区间[ L , R ]的差值不会超过 \(1e6\),这就是破题点。
我们需要改进线性筛法,得到一个可以求得尽管数据很大但是实际区间长度不大的质数筛法,即二次筛法。
在前面已经提到检验一个数\(n\)是否为素数只需要判断前\(\sqrt n\)个数即可,所以实际上判断\(2^{31}\)的数只需要判断不到\(50000\)的数据。
于是我们得到这样一个 算法,使用欧拉筛筛选1~50000的素数,而后对于每一个给出的区间使用埃式筛筛选素数。
vector<ll> prime;
const ll maxn = 1e6+50;
bitset<maxn> vis;
const ll N = 1e6+50;
bool is_p[N] = {false};
void euler(){
vis.reset();
for(ll i = 2;i < maxn;i++){
if(!vis[i]) prime.push_back(i);
for(ll j = 0;j < prime.size() && i*prime[j] < maxn;j++) {
vis[i * prime[j]] = 1; //找到质数倍
if (i % prime[j] == 0)break;
}
}
}
int main() {
euler();
ll l,r;cin>>l>>r;
if(l == 1)l++;
for(ll i = 0;prime[i]*prime[i] <= r;i++){
ll p = prime[i];
for(ll j = max(2*1ll,(l-1)/p+1)*p;j <= r;j += p){
if(j >= l)is_p[j-l] = true;
}
}
ll tot = 0;
for(ll i = l;i<=r;i++) if(!is_p[i-l])++tot;
cout<<tot<<endl;
return 0;
}
质因子
关注了素数之后我们一样需要关注合数。
合数主要问题在于因数。
首先给出一个定理:
唯一分解定理(算数基本定理)
除1以外的的所有数都可以被分解为若干质数相乘
\(n = \prod \limits_{i=1}^mp_i^{c^i}\)
证明
朴素质因数分解算法
枚举因子,一个个的除即可,时间复杂度\(O(\sqrt n)\)
一道牛客多校的题目
Pollard-Rho 大质因数分解
P4718 【模板】Pollard-Rho算法
\(Pollard~Rho\)是一个非常玄学的方式,用于在\(O(n^{1/4})\)的期望时间复杂度内计算合数n的某个非平凡因子。事实上算法导论给出的是\(O(\sqrt p)\),\(p\)是\(n\)的某个最小因子,满足\(p\)与\(n/p\)互质。但是这些都是期望,未必符合实际。但事实上Pollard Rho算法在实际环境中运行的相当不错。
Pollard-Rho的原理就是通过某种方法得到两个整数\(a\)和\(b\),而待分解的大整数为\(n\),计算 \(p=gcd(abs(a-b,n))\),直到\(p\)不为1或者\(a,b\)出现循环为止。然后判断\(p\)是否为\(n\),如果\(p=n\)或者\(p=1\)成立,则\(n\)为质数,否则\(p\)是\(n\)的一个因数,于是递归的找\(Pollard(n/p)\)。
通常使用生成函数\(x[i+1] = (x[i]*x[i]+c) \mod n\)迭代计算\(a\)和\(b\),通常取\(c\)为1。当\(a\)和\(b\)出现循环时,进行判断。(该生成函数是必然存在循环的)。
现在问题是如何判断循环,这是一种类似与龟兔赛跑的模型,假设有一个环形跑道我们让\(b\)的速度是\(a\)的两倍当\(b\)第一次赶上\(a\)时我们就知道产生了循环。
OIwiki的板子:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <time.h>
#define ll long long
using namespace std;
ll gcd(ll a,ll b){return b?gcd(b,a%b):a;}
ll mul(ll a, ll b, ll p) {//慢速乘似乎过不了
// ll ans = 0;
// while (b) {
// if (b & 1) ans += a, ans %= p;
// a <<= 1, a %= p;
// b >>= 1;
// }
// return ans%p;
return (__int128)a*b%p;
}
ll f_pow(ll a,ll b,ll mod){
ll res = 1;a%=mod;
while(b){
if(b&1)res = mul(res,a,mod);
a = mul(a,a,mod);
b>>=1;
}
return res;
}
bool Miller_Rabin(ll p) { //判断素数
if(p < 2) return false;
if(p == 2 || p == 3) return true;
ll d = p - 1, r = 0;
while (!(d & 1)) ++r, d >>= 1; //将d处理为奇数
for (ll k = 0; k < 10; ++k) {
ll a = rand() % (p - 2) + 2;
ll x = f_pow(a,d,p);
if(x == 1 || x == p - 1) continue;
for(ll i = 0;i < r;i++){
x = mul(x,x,p);
if(x == p-1)break;
}
if(x != p-1) return false;
}
return true;
}
ll pollard_rho(ll x) {
ll s = 0, t = 0;
ll c = 1ll*rand() % (x - 1) + 1;
int step, goal;
ll val = 1;
for (goal = 1;; goal <<= 1, s = t, val = 1) {//倍增优化
for (step = 1; step <= goal; ++step) {
t = (mul(t,t,x)+c)%x;
val = mul(val , abs(t - s) , x);
if ((step % 127) == 0) {//似乎是倍增的上线
ll d = gcd(val, x);
if (d > 1) return d;
}
}
ll d = gcd(val, x);
if (d > 1) return d;
}
}
ll ans = -1;
void find(ll n){
if(n <= ans || n < 2) return ;
if(Miller_Rabin(n)){//如果要找所有的质因子,修改这里
//ans = max(ans,n);
ans = ans >= n?ans:n;
//cout<<n<<endl;
return;
}
ll p = n;
while(p >= n)p = pollard_rho(n);
while((n%p) == 0) n/=p;
find(n),find(p);
}
void solve(ll n){
find(n);
if(ans == n)puts("Prime");
else printf("%lld\n",ans);
}
int main(){
//ios::sync_with_stdio(false);
ll t;scanf("%lld",&t);
while(t--) {
//srand((unsigned)time(NULL));
ll n;scanf("%lld",&n);
ans = -1;
solve(n);
}
return 0;
}
威尔逊定理
若\(p\)为素数,则\((p-1)! \equiv -1(\mod p)\)
同时逆定理也成立,即\((p-1)! \equiv -1(\mod p)\),则\(p\)为素数。
费马小定理
若\(p\)为素数,\(a\)为正整数,且\(a\)和\(p\)互质,则:\(a^{p-1}\equiv 1(\mod p)\)。
证明;
\(p-1\)个整数\(a,2a,3a,...,(p-1)a\)中没有一个时\(p\)的倍数。
同时,这\(p-1\)个数不存在两个数同余于模\(p\)。
因此他们对\(p\)的同余一定是\(1\)~\((p-1)\)的一种排列(鸽巢原理)。
故,\(a*2a*3a*....*(p-1)a\)\(\equiv\) \(1*2*3*...*(p-1)(\mod p)\)
即,\(a^{p-1}*(p-1)!\equiv(p-1)!(\mod p)\)
根据威尔逊定理\((p-1)!\)和\(p\)互质,可得 \(a^{p-1} \equiv 1(\mod p)\)
在一般情况下,还有:
设\(p\)为素数且\(a\)是一个正整数,则\(a^p \equiv a(\mod p)\)
欧拉定理
定义欧拉函数\(\varphi(n)\):
表示小于等于\(n\)和\(n\)互质的个数。
公式的证明在容斥定理中给出
ll phi[N];
vector<int> prime;
const ll maxn = 1e5;
bitset<maxn> vis;
void euler(){
vis.reset();
for(int i = 2;i < maxn;i++){
if(!vis[i]) prime.push_back(i),phi[i] = i-1;
for(int j = 0;j < prime.size() && i*prime[j] <= maxn;j++) {
vis[i * prime[j]] = true; //找到质数倍
if (i % prime[j] == 0){
phi[i*prime[j]] = phi[i]*prime[j];
break;
}else phi[i*prime[j]]=phi[i]*(prime[j]-1);
}
}
}
引理:
- 如果\(n\)为一个素数\(p\),\(\varphi(p)\)=\(p-1\)
- 如果\(n\)为一个素数\(p^a\),\(\varphi(p)\)=\((p-1)p^{a-1}\)
- 如果\(a,b\)互质,那么\(\varphi(ab)\)=\(\varphi(a)\)\(\varphi(b)\)
证明:
1.枚举即可
2.我们可以反过来考虑,所有的可以被\(p\)整除的数,可以看作\(p*t\),\(t\)的最大值就是可以被\(p\)整除的数的个数,可得\(t = p^{a-1}-1\),而比\(p^a\)小的数字有 \(p^a-1\)。
3.与\(ab\)互质的数要么与\(a\)互质共\(\varphi(a)\)个,那么与\(b\)互质共\(\varphi(b)\)个,故\(\varphi(ab)\) = \(\varphi(a)\)\(\varphi(b)\)
欧拉定理:若\(a\)与\(m\)互质,则\(a^{\varphi(m)}\equiv1(\mod m)\)
P2158
\(gcd\)和\(lcm\)
\(gcd(a,b)\)表示\(a,b\)的最大公约数,\(lcm(a,b)\)表示\(a,b\)的最小公倍数
求解\(gcd\)与\(lcm\)
不难证明,\(gcd(a,b)*lcm(a,b) == ab\),所以求得\(gcd(a,b)\)即可计算\(lcm(a,b)\)
欧几里得算法
存在性质,\(gcd(a,b)=gcd(b,a\mod b)\)。
证明
代码
ll gcd(ll a,ll b){
return b == 0?a:gcd(b,a%b);
}
更相减损术
存在性质,\(gcd(a,b) = gcd(a,a-b)\)
基于性质可递归写出代码。
时间复杂度不如欧几里得算法,但是该性质是有用的。
这篇题解的题目就用到了这个性质
题解也提到了一些性质,这里就不说了。
特别的
我们将\(gcd(a,b)=1\)时,称\(a,b\)互质。