质数
试除法判断质数(素数)( O ( s q r t ( n ) O(sqrt(n) O(sqrt(n))
质数一定是严格大于一的整数,如果只包含1和本身这两个约数,就成为质数(素数)
#include <iostream>
#include <algorithm>
using namespace std;
bool is_prime(int x)
{
if (x < 2) return false;//注意是质数的条件
for (int i = 2; i <= x / i; i ++ )//注意循环条件
if (x % 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;
}
试除法分解质因数( O ( l o g n ) O(logn) O(logn)~ O ( s q r t ( n ) O(sqrt(n) O(sqrt(n))
算术基本定理:任何一个大于1的自然数 N N N,如果 N N N不为质数 ,那么 N N N可以唯一分解成有限个质数的乘积 N = p 1 a 1 ∗ p 2 a 2 ∗ … ∗ p n a n N={p_1^{a_1}}∗{p_2^{a_2}}*…∗{p_n^{a_n}} N=p1a1∗p2a2∗…∗pnan
且最多只有一个大于 n \sqrt[]{n} n的质因子,这里 P 1 < P 2 < P 3 . . . . . . < P n P_1<P_2<P_3......<P_n P1<P2<P3......<Pn均为质数,其中指数 a i a_i ai是正整数。
时间复杂度: n \sqrt[]{n} n
#include <iostream>
#include <algorithm>
using namespace std;
void divide(int x)
{
for (int i = 2; i <= x / i; i ++ )//先枚举出所有小于根号x的质因子
if (x % i == 0)
{
int s = 0;
while (x % i == 0) x /= i, s ++ ;
cout << i << ' ' << s << endl;
}
if (x > 1) cout << x << ' ' << 1 << endl;//最多只有一个大于根下x的质因子(两个相乘就大于x了)
cout << endl;
}
int main()
{
int n;
cin >> n;
while (n -- )
{
int x;
cin >> x;
divide(x);
}
return 0;
}
对于最好时间复杂度 log n \log{n} logn的分析:
当n为 2 k 2^k 2k时,一直在i=2时循环直至n=1,此时时间为 log n \log{n} logn
筛质数
朴素筛质数方法( O ( n l o g n ) O(nlogn) O(nlogn))
对前n个数进行遍历,同时将当前数i的倍数去除,剩下的即为1~n之间的质数
时间复杂度的分析: 对于第i个数,在1~n中共有n/i个倍数,为 n 2 + n 3 + n 4 + … … + n n = n ∗ ( ln n + C ) ( 调 和 级 数 ) {n \over 2}+{n \over 3}+{n \over 4}+……+{n \over n} =n*(\ln{n}+C)(调和级数) 2n+3n+4n+……+nn=n∗(lnn+C)(调和级数)约为 n ∗ log n n*\log{n} n∗logn
#include <iostream>
#include <algorithm>
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]) continue;
primes[cnt ++ ] = i;//存储质数
for (int j = i + i; j <= n; j += i)//不管是合数还是质数,都用来筛掉后面它的倍数
st[j] = true;
}
}
int main()
{
int n;
cin >> n;
get_primes(n);
cout << cnt << endl;
return 0;
}
埃拉托斯特尼筛法(埃氏筛) O ( n l o g l o g n ) O(nloglogn) O(nloglogn)
对前n个数进行遍历,同时将质数i的倍数去除,剩下的即为1~n之间的质数
时间复杂度的分析:
**质数定理:**1~n中有 n ln n n \over {\ln{n}} lnnn个质数
对于第i个数,在1~n中共有n/i个倍数,为 ( n 2 + n 3 + n 4 + … … + n n ) ∗ n ln n = n ∗ n ∗ ( ln n + C ) ( 调 和 级 数 ) ln n ({{{n \over 2}+{n \over 3}+{n \over 4}+……+{n \over n})*n }\over {\ln{n}}} ={n *n*(\ln{n}+C)(调和级数)\over {\ln{n}}} (lnn2n+3n+4n+……+nn)∗n=lnnn∗n∗(lnn+C)(调和级数)约为 n ∗ log log n n*\log{\log {n}} n∗loglogn
void get_primes(){
for(int i=2;i<=n;i++){
if(!st[i]){
primes[cnt++]=i;
for(int j=i;j<=n;j+=i) st[j]=true;//可以用质数就把所有的合数都筛掉;
}
}
}
线性筛法( O ( n ) O(n) O(n))
算法核心:x仅会被其最小质因子筛去
每个数一定只有一个最小质因子,所以每个数只会被筛一次所以为线性
个人对于线性筛的分析:
1.为何令循环中 s t [ p r i m e s [ j ] ∗ i ] = t r u e st[primes[j] * i] = true st[primes[j]∗i]=true?
根据算术基本定理,对于一个合数x,可以写成多个质数相乘的形式,及primes[j]*i,无论i为合数或质数都可由质数组成,所以 s t [ p r i m e s [ j ] ∗ i ] = t r u e st[primes[j] * i] = true st[primes[j]∗i]=true一句可以将所有的合数筛掉
2.for循环的循环条件为何是 p r i m e s [ j ] < = n / i primes[j] <= n / i primes[j]<=n/i?
由第一条可知,primes[j]*i是将前n个数中的所有合数筛出,所以primes[j]*i所代表的合数应该是小于等于n的
3.此算法为何为线性?
关键在于跳出循环一句,由y总分析我们可知,当primes[j]是i的最小质因子时跳出循环,保证了每个数最多被筛一次,所以将时间将为了线性
#include <iostream>
#include <algorithm>
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;//i为当前的最大质数
for (int j = 0; primes[j] <= n / i; j ++ )//从小到大枚举所有质数,i为当前的最大质数,所以n/i为可能的最大的最小质因数,所以pj应小于n/i
{
//对于任意一个合数x,假设pj为x最小质因子,i在枚举到x之前一定会枚举到x/pj(合数x的最大质因数),当i枚举到x/pj时一定会被筛掉
st[primes[j] * i] = true;
if (i % primes[j] == 0) break;//primes[j]一定是i的最小质因子
//i % primes[j] == 0 ,primes[j]一定是i的最小质因子,primes[j]也一定时primes[j]*i的最小质因子
//i % primes[j] != 0 ,primes[j]一定小于i的最小质因子
}
}
}
int main()
{
int n;
cin >> n;
get_primes(n);
cout << cnt << endl;
return 0;
}
约数
试除法求约数
一个整数的约数个数上界为 2 n 2\sqrt[]{n} 2n
int范围内的所有整数中约数最多的大概在1500~1600
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
vector<int> get_divisors(int x)
{
vector<int> res;
for (int i = 1; i <= x / i; i ++ )
if (x % i == 0)
{
res.push_back(i);
if (i != x / i) res.push_back(x / 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 x : res) cout << x << ' ';
cout << endl;
}
return 0;
}
时间复杂度的分析:
对于每个数求约数:
由于循环枚举 2 2 2到 n \sqrt{n} n,所以时间复杂度为 n \sqrt{n} n
对n个数求约数:
对于前n个数,第i个数是其所有倍数 k ∗ i ( k ∗ i ≤ n ) k*i({k*i}\leq{n}) k∗i(k∗i≤n)的所有约数,所以1~n中所有倍数的个数和所有约数的个数相同,所以前n’个数总共有 n 1 + n 2 + … … n n = n ∗ ln n {{n}\over{1}}+{n\over2}+……{n\over{n}}=n*\ln{n} 1n+2n+……nn=n∗lnn个约数,所以平均每个数由接近 log n \log{n} logn个约数
所以对n个数求约数且排序的时间复杂度为 log n ∗ log log n \log{n}*\log{\log{n}} logn∗loglogn
所以总的时间复杂度为 n \sqrt{n} n
约数个数
由算术基本定理得:
N = p 1 a 1 ∗ p 2 a 2 ∗ p 3 a 3 ∗ … … ∗ p k a k N={p_1^{a_1}}*{p_2^{a_2}}*{p_3^{a_3}}*……*{p_k^{a_k}} N=p1a1∗p2a2∗p3a3∗……∗pkak
所以,对于 N N N的每一个约数k都可以用 k = p 1 b 1 ∗ p 2 b 2 ∗ p 3 b 3 ∗ … … ∗ p k b k ( 0 ≤ b i ≤ a i ) k={p_1^{b_1}}*{p_2^{b_2}}*{p_3^{b_3}}*……*{p_k^{b_k}}({0}\leq{b_i}\leq{a_i}) k=p1b1∗p2b2∗p3b3∗……∗pkbk(0≤bi≤ai)的形式表示,所以 N N N的约数个数为 s u m = ( a 1 + 1 ) ∗ ( a 2 + 1 ) ∗ … … ∗ ( a k + 1 ) sum=(a_1+1)*(a_2+1)*……*(a_k+1) sum=(a1+1)∗(a2+1)∗……∗(ak+1)
题解:
对于由多个数相乘得到的数 N N N求其约数个数,我们可以先依次求出每个乘数的 a 1 , a 2 … … a i a_1,a_2……a_i a1,a2……ai,统计后套入公式,因为 N N N根据算术基本定理展开后是与乘数展开式相关的式子(若p相同则p的指数相加)
#include <iostream>
#include <algorithm>
#include <unordered_map>
#include <vector>
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] ++ ;//注意大于sqrt(x)的约数
}
LL res = 1;
for (auto p : primes) res = res * (p.second + 1) % mod;
cout << res << endl;
return 0;
}
约数之和
由算术基本定理得:
N = p 1 a 1 ∗ p 2 a 2 ∗ p 3 a 3 ∗ … … ∗ p k a k N={p_1^{a_1}}*{p_2^{a_2}}*{p_3^{a_3}}*……*{p_k^{a_k}} N=p1a1∗p2a2∗p3a3∗……∗pkak
所以,对于 N N N的每一个约数k都可以以 k = p 1 b 1 ∗ p 2 b 2 ∗ p 3 b 3 ∗ … … ∗ p k b k ( 0 ≤ b i ≤ a i ) k={p_1^{b_1}}*{p_2^{b_2}}*{p_3^{b_3}}*……*{p_k^{b_k}}({0}\leq{b_i}\leq{a_i}) k=p1b1∗p2b2∗p3b3∗……∗pkbk(0≤bi≤ai)的形式表示,所有约数的和整理得 ( p 1 0 + p 1 1 + p 1 2 + … … ∗ p 1 a 1 ) ∗ ( p 2 0 + p 2 1 + p 1 2 + … … ∗ p 2 a 2 ) ∗ … … ∗ ( p k 0 + p k 1 + p k 2 + … … ∗ p k a k ) = ∏ i = 1 k ( ∑ j = 0 a i ( p i ) j ) ({p_1^{0}}+{p_1^{1}}+{p_1^{2}}+……*{p_1^{a_1}})*({p_2^{0}}+{p_2^{1}}+{p_1^{2}}+……*{p_2^{a_2}})*……*({p_k^{0}}+{p_k^{1}}+{p_k^{2}}+……*{p_k^{a_k}})=\prod_{i=1}^{k}({\sum_{j=0}^{a_i}{({p_i})^j})} (p10+p11+p12+……∗p1a1)∗(p20+p21+p12+……∗p2a2)∗……∗(pk0+pk1+pk2+……∗pkak)=i=1∏k(j=0∑ai(pi)j)
#include <iostream>
#include <algorithm>
#include <unordered_map>
#include <vector>
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;
res = res * t % mod;
}
cout << res << endl;
return 0;
}
最大公约数(欧几里得算法/辗转相除法( O ( l o g ( a + b ) ) O(log(a+b)) O(log(a+b))))
原理:
若 d ∣ a d|a d∣a(d能整除a), d ∣ b d|b d∣b(d能整除b),
则 d ∣ ( a ∗ x + b ∗ y ) d|(a*x+b*y) d∣(a∗x+b∗y)
则 g c d ( a , b ) = g c d ( b , a m o d b ) gcd(a,b)=gcd(b,a mod b) gcd(a,b)=gcd(b,amodb)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int gcd(int a,int b){
return b ? gcd(b,a%b) : a;//0和a的最大公约数是a
}
int main(){
cin>>n;
while(n--){
int a,b;
cin>>a>>b;
cout<<gcd(a,b)<<'\n';
}
return 0;
}
欧拉函数
欧拉定理:
若a和n互质,则 a ϕ ( n ) ≡ 1 ( m o d n ) a^{\phi{(n)}}{\equiv}1(\mod n) aϕ(n)≡1(modn)
证明:
假设在1~n中与n互质的数为 a 1 , a 2 … … a ϕ ( n ) a_1,a_2……a_{\phi(n)} a1,a2……aϕ(n)所以 a 1 ∗ a , a 2 ∗ a … … a ϕ ( n ) ∗ a a_1*a,a_2*a……a_{\phi(n)}*a a1∗a,a2∗a……aϕ(n)∗a也都与n互质
由以上两引理得以上两组数是同一组数,即两组数得乘积相等 a 1 ∗ a 2 ∗ … … ∗ a ϕ ( n ) ≡ a ϕ ( n ) ∗ a 1 ∗ a 2 ∗ … … ∗ a ϕ ( n ) ( m o d n ) a_1*a_2*……*a_{\phi(n)}{\equiv} a^{\phi{(n)}}*a_1*a_2*……*a_{\phi(n)}(\mod {n}) a1∗a2∗……∗aϕ(n)≡aϕ(n)∗a1∗a2∗……∗aϕ(n)(modn)化简得 a ϕ ( n ) ≡ 1 ( m o d n ) a^{\phi{(n)}}{\equiv}1(\mod n) aϕ(n)≡1(modn)
费马小定理:
a n − 1 ≡ 1 ( m o d n ) a^{n-1}{\equiv}1(\mod n) an−1≡1(modn)(n是质数)
关于欧拉函数的证明暂不做考虑,在这里有大佬的[证明](太菜了根本不会证明,以后慢慢补吧)
朴素做法——求N个数的欧拉函数( O ( N X ) O(N\sqrt{X}) O(NX))
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main(){
int n;
cin>>n;
while(n--){
int x;
cin>>x;
ll 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);//先除后乘防止爆数据
cout<<res<<'\n';
}
return 0;
}
线性筛求欧拉函数——求前N个数的欧拉函数( O ( n ) O(n) O(n))
ϕ ( 1 ) = 1 \phi(1)=1 ϕ(1)=1
如果N为质数,则 ϕ ( N ) = N ∗ ( 1 − 1 N ) = N − 1 \phi(N)={N*(1-{1\over{N}})}=N-1 ϕ(N)=N∗(1−N1)=N−1
如果pj是i的最小质因数,则 ϕ ( p j ∗ i ) = p j ∗ ϕ ( i ) \phi(pj*i)={pj*\phi(i)} ϕ(pj∗i)=pj∗ϕ(i)
如果pj不是i的质因数,则 ϕ ( p j ∗ i ) = p j ∗ ( 1 − 1 p j ) ϕ ( i ) = ( p j − 1 ) ∗ ϕ ( i ) \phi(pj*i)={pj*{(1-{1\over{pj}})}\phi(i)}=(pj-1)*\phi(i) ϕ(pj∗i)=pj∗(1−pj1)ϕ(i)=(pj−1)∗ϕ(i)
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 1000010;
int primes[N], cnt;
int euler[N];
bool st[N];
void get_eulers(int n)
{
euler[1] = 1;//1的欧拉函数值为1
for (int i = 2; i <= n; i ++ )
{
if (!st[i])
{
primes[cnt ++ ] = i;
euler[i] = i - 1;//质数N的欧拉函数值为N*(1-1/N)=N-1
}
for (int j = 0; primes[j] <= n / i; j ++ )
{
int t = primes[j] * i;
st[t] = true;
if (i % primes[j] == 0)
{
euler[t] = euler[i] * primes[j];//如果pj是i的最小质因数
break;
}
euler[t] = euler[i] * (primes[j] - 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;
}
快速幂( O ( log k ) O(\log{k}) O(logk))
解决问题:
快速的求出 a k % p {a^k}\%{p} ak%p的值( O ( log k ) O(\log{k}) O(logk))
基本思路:
1.预处理出 a 2 0 , a 2 1 , a 2 2 , … … a 2 log k a^{2^0},a^{2^1},a^{2^2},……a^{2^{\log{k}}} a20,a21,a22,……a2logk
2.将 a k a^k ak用预处理出的数据表示(很明显得将k化为二进制形式则能找出对应的表示方式)
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
LL qmi(int a, int b, int p)
{
LL res = 1 % p;
while (b)
{
if (b & 1) res = res * a % p;//b&1判断b的二进制表示第0位是否为1
a = a * (LL)a % p;//注意int会爆
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;
}
快速幂与费马小定理的简单应用
#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;
}
扩展欧几里得算法
裴蜀定理:
对于任意正整数a,b一定存在非零整数x,y,使得 a ∗ x + b ∗ y = g c d ( a , b ) a*x+b*y=gcd(a,b) a∗x+b∗y=gcd(a,b)
g c d ( a , b ) gcd(a,b) gcd(a,b)是a和b的约数,所以 a x + b y ax+by ax+by一定是 g c d ( a , b ) gcd(a,b) gcd(a,b)的倍数,所以 g c d ( a , b ) gcd(a,b) gcd(a,b)是 a x + b y ax+by ax+by能凑出的最小正整数
欧几里得算法:
解决问题:
用于求解两数a,b的最大公约数 g c d ( a , b ) = g c d ( b , a % b ) gcd(a,b)=gcd(b,a\%{b}) gcd(a,b)=gcd(b,a%b)
int gcd(int a,int b){
return b ? gcd(b,a%b) : a;//0和a的最大公约数是a
}
扩展欧几里得算法:
解决问题:
用于求解方程 a ∗ x + b ∗ y = g c d ( a , b ) a*x+b*y=gcd(a,b) a∗x+b∗y=gcd(a,b)的解
分析思路:
1.当b=0时,即 a ∗ x + 0 ∗ y = g c d ( a , 0 ) = a a*x+0*y=gcd(a,0)=a a∗x+0∗y=gcd(a,0)=a显然x,y的一组解为 x = 1 , y = 0 x=1,y=0 x=1,y=0
2.当b!=0时, 因为 g c d ( a , b ) = g c d ( b , a % b ) gcd(a,b)=gcd(b,a\%b) gcd(a,b)=gcd(b,a%b)
而 b x ′ + ( a % b ) y ′ = g c d ( b , a % b ) bx'+(a\%b)y'=gcd(b,a\%b) bx′+(a%b)y′=gcd(b,a%b) b x ′ + ( a − ⌊ a / b ⌋ ∗ b ) y ′ = g c d ( b , a % b ) bx'+(a-\lfloor{a/b}\rfloor{*b})y'=gcd(b,a\%b) bx′+(a−⌊a/b⌋∗b)y′=gcd(b,a%b) a y ′ + b ( x ′ − ⌊ a / b ⌋ ∗ y ′ ) = g c d ( b , a % b ) = g c d ( a , b ) ay'+b(x'-\lfloor{a/b}\rfloor{*y'})=gcd(b,a\%b)=gcd(a,b) ay′+b(x′−⌊a/b⌋∗y′)=gcd(b,a%b)=gcd(a,b)所以 x = y ′ , y = x ′ − ⌊ a / b ⌋ ∗ y ′ x=y',y=x'-\lfloor{a/b}\rfloor{*y'} x=y′,y=x′−⌊a/b⌋∗y′
#include <iostream>
#include <algorithm>
using namespace std;
int exgcd(int a, int b, int &x, int &y)//注意x,y引用,函数类型为int
{
if (!b)
{
x = 1, y = 0;
return a;
}
int d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
int main()
{
int n;
scanf("%d", &n);
while (n -- )
{
int a, b;
scanf("%d%d", &a, &b);
int x, y;
exgcd(a, b, x, y);
printf("%d %d\n", x, y);
}
return 0;
}
对于一般的方程 a x + b y = c ax+by=c ax+by=c
通解为 x = x ′ + k ∗ b / d , y = y ′ − k ∗ a / d ( d = g c d ( a , b ) , k ∈ Z ) x=x'+k*b/d,y=y'-k*a/d(d=gcd(a,b),k\in{Z}) x=x′+k∗b/d,y=y′−k∗a/d(d=gcd(a,b),k∈Z)即只需求出一组解x’,y’就能得到所有解
拓展欧几里得算法的应用——线性同余方程
题目分析:
根据裴蜀定理可知,只有当b时 g c d ( a , m ) gcd(a,m) gcd(a,m)的倍数时 a x + m y = b ax+my=b ax+my=b才有解
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
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);
y -= a / b * x;
return d;
}
int main()
{
int n;
scanf("%d", &n);
while (n -- )
{
int a, b, m;
scanf("%d%d%d", &a, &b, &m);
int x, y;
int d = exgcd(a, m, x, y);
if (b % d) puts("impossible");
else printf("%d\n", (LL)b / d * x % m);//b是gcd(a,m)的b/d倍
}
return 0;
}