数论 _ 质数和约数
质数
判定质数
试除法判断素数
复杂度: \(O ( \sqrt n )\) (每次一定是\(\sqrt n\) )
原理: 约数成对出现(完全平方数除外)
代码:
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;
}
注意点:
for循环的条件建议是: i<= x / i;
因为
i*i <= x
可能i*i会爆int(long long)i<=sqrt(x)
每次调用sqrt函数可能会超时。
分解质因数 | 求解最大质因数
暴力写法:
就是枚举从2到x的每一个数,如果是x的因子,那么就除去他。
需要注意的是:
因为一个合数的质因子一定在扫描到这个合数之前就从x中剔除掉了,所以无需验证 i 是否为质数
代码:
void divide(int x)
{
for (int i = 2; i <= x; i ++ )
if (x % i == 0)
{
int s = 0;
while (x % i == 0) x /= i, s ++ ;
cout << i << ' ' << s << endl;
}
}
然后有条性质:
n的因子中,最多只包含一个大于sqrt (n) 的质因子。
证明: 如果有两个,那么这两个质数相乘就大于n了。
所以我们可以将代码改为:
暴力2到\(\sqrt n\) ,然后单独处理大于\(\sqrt n\)的那一个。
模板代码
上限复杂度: \(O ( \sqrt n )\)
下限复杂度: \(O ( \log n )\)
代码:
void divide(int x)
{
for (int i = 2; i <= x / i; i ++ )
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;
cout << endl;
}
筛质数
欧拉线性质数筛
原理
基本思想 :
在埃氏筛法的基础上,让每个合数只被它的 最小质因子×另一个数 筛选一次,以达到不重复的目的。
关键:
if(i%prime[j]==0)break;
理解:
- 当 i是prime[j]的倍数时,$I_1 =k × prime[j] $
- 如果继续运算 j+1,就会筛掉
- 当\(I_2 = k × prime[j+1]\)时,还会运行到
- 就重复筛了,所以跳出循环,保证每个数只筛选一次。
比如说:
对于筛选90=5×3×3×2=45×2。我们目的是保证90只被他的最小质因子2筛选一次。
当i=30=5×3×2时,因为30的最小质因数是2,
所以当质数为3时、筛选的数是30×3=90,
而对于30×3:因为 30的最小质因数是2 所以一定可以找到30×3=另一个数×2。
所以当i%prime[j]==0时,需要结束掉。
代码
int primes[N], top=0;//存储素数
bool st[N];//记录是否为素数
void get_prime(int n)
{
st[1] = true;
for (int i = 2; i <= n; i++)
{
if (!st[i]) primes[top++] = i;
for (int j = 0; j *i <=n && j < top; j++)
{
st[i * primes[j]] = true;
if (i % primes[j]==0)//关键
break;
}
}
}
代码可以再简单一下把j < top这个条件去掉。
int primes[N], top=0;//存储素数
bool st[N];//记录是否为素数
void get_prime(int n)
{
st[1] = true;
for (int i = 2; i <= n; i++)
{
if (!st[i]) primes[top++] = i;
for (int j = 0; j *i <=n ; j++)
{
st[i * primes[j]] = true;
if (i % primes[j]==0)//关键
break;
}
}
}
素数定理
[1,N]中素数的个数约为NlgN。
则从[1,N]中人选一个数,其为质数的概率为1/lgN。
算数基本定理
任意一个整数都能被分解为如下形式:
\(n=p^{k_{1}}_{1}×p^{k_{2}}_{2}×....×p^{k_{t}}_{t}\)。 其中p为质数。
约数
小知识:
int 范围内约数最多的数的个数为 1536,那个数为 1745944200
求所有约数
试除法求约数
和求质因子差不多,直接看代码
复杂度:\(O(\sqrt n )\)
代码:
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;
}
约数个数
问题:求一个数的约数个数
前置知识:算数基本定理
任意一个整数都能被分解为如下形式:
\(n=p^{k_{1}}_{1}×p^{k_{2}}_{2}×....×p^{k_{t}}_{t}\)。 其中p为质数。
如果n被分解成上面的形式,由乘法原理,得约数个数就是
应用:求n个数乘积的约数个数
给定 n 个正整数 ai,请你输出这些数的乘积的约数个数
思路:
分别求出每一个的,然后把每一个数的指数累加即可。
代码:
#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) res = res * (p.second + 1) % mod;
cout << res << endl;
return 0;
}
约数之和
问题:求一个数的约数之和
前置知识:算数基本定理
任意一个整数都能被分解为如下形式:
\(n=p^{k_{1}}_{1}×p^{k_{2}}_{2}×....×p^{k_{t}}_{t}\)。 其中p为质数。
如果n被分解成上面的形式,约数之和显然是
\((p_1^0 + p_1^1 + ... + p_1^{k1}) ×...× (p_t^0 + p_t^1 + + p_tt^{kt})\)
应用:求n个数乘积的约数之和
给定n个正整数,请你输出这些数的乘积的约数之和
思路:
分别求出每一个的,然后最终集中带入公式即可。
代码:
#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;
}
求是X的约数的个数(数的值可重复)
题意: 共有 N 个整数 A1,A2,…,AN,对于每一个数 Ai,求其他的数中有多少个是它的约数。
思路: 对于每个数判断其他数是不是他的倍数。
1291. 轻拍牛头
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
typedef pair<int, int> pii;
const int N = 1e6 + 10;
const int INF = 0x3f3f3f3f3f;
int n;
int a[N];
int cnt[N],st[N];
void slove()
{
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
st[a[i]]++;
}
for(int i=1;i<=N;i++){
if(!st[i]) continue;
cnt[i]+=st[i]-1;
for(int j=i+i;j<=N;j+=i){
if(st[j])
cnt[j]+=st[i];
}
}
for(int i=1;i<=n;i++){
cout<<cnt[a[i]]<<endl;
}
}
signed main()
{
// int t;
// cin >> t;
// while (t--)
slove();
return 0;
}
求n!的约数个数
1294. 樱花
题意
通过化简可以得到题意是求:$(n!)^2 $ 的约数个数
思路:
看代码吧
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
#define int long long
const int N =1e6+10;
int mod=1e9+7;
int n;
int primes[N],cnt;
bool st[N];
void getprimes(){
for(int i=2;i<=n;i++){
if(!st[i]) primes[cnt++]=i;
for(int j=0;primes[j]*i<=n;j++){
st[primes[j]*i]=true;
if(i%primes[j]==0) break;
}
}
}
signed main()
{
cin>>n;
int ans=1;
getprimes();
// cout<<ans<<endl;
for(int i=0;i<cnt;i++){
int p=primes[i];
int s=0;
for(int j=n;j;j/=p) s+=j/p;//求n!的约数个数
ans=(ans*(s*2+1)%mod)%mod;//求n!的平方的约数的个数
}
cout<<ans<<endl;
return 0;
}
198. 反素数
代码:
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
typedef pair<int, int> pii;
const int N = 1e6 + 10;
const int INF = 0x3f3f3f3f3f;
int primes[] = {2, 3, 5, 7, 11, 13, 17, 19, 23};
int n;
int maxd, number;
//u: 当前的质因子
//last:上一个质因子的次方数(也就是这次dfs次方的最大值)
//p:这次dfs之前的 因子的乘积
//s:约数的个数
void dfs(int u, int last, int p, int s)
{
int k = primes[u];
if (s > maxd || s == maxd && p < number)
{
maxd = s;
number = p;
}
if (u == 9)
return;
for (int i = 1; i <= last; i++)
{
if (p * primes[u] > n)
break;
p *= primes[u];
dfs(u + 1, i, p, s * (i + 1));
//s * (i + 1)是因为求的是所有的约数,比如
//对于18,(3,9),(2,6,18)...都是约数
}
}
void slove()
{
cin >> n;
dfs(0, 30, 1, 1);
cout << number << endl;
}
signed main()
{
// int t;
// cin >> t;
// while (t--)
slove();
return 0;
}