一些知识
整除
自然数a可以被自然数b整除或者说b是a的约数表示的是:
a
%
b
=
0
a\%b=0
a%b=0,即a是b的倍数,b是a的约数(不要弄反了),记作b|a。
(0是自然数也是整数)
质数(素数)和合数
指在大于1的自然数中,除了1和它本身不再有其他因数的自然数(如:2,3,5);一个大于1的整数,如果除了1和它本身以外,还有其他的约数,这样的数就叫作合数。
素数的分布
目前素数的分布还不能确定,但是能够给出一个近似分布,
π
(
n
)
\pi(n)
π(n)为不超过n的质数的个数,
π
(
n
)
∼
n
l
n
n
\pi(n) \sim \frac{n}{lnn}
π(n)∼lnnn
证明灰常的复杂,感兴趣的可以看这里
质因数
对于给定的自然数n,x是其质因数,当且x是n的因数且x是质数。
最大公因数
记作gcd()。
最小公倍数
记作lcm()。
互质
如果两个或两个以上的整数的最大公约数为1,则他们互质,记作 a ⊥ b a \bot b a⊥b
唯一分解定理
任何一个大于1的数都可以被分解为有限个质数乘积的形式,并且分解方式是唯一的,
质数是一个整数最基本的特征,很多问题都从质数的角度考虑
如:300=22355 12=223
一个结论:
若
a
=
q
1
n
1
q
2
n
2
a=q_1^{n_1} q_2^{n_2}
a=q1n1q2n2,且b|a,则
b
=
q
1
b
1
q
2
b
2
b=q_1^{b_1} q_2^{b_2}
b=q1b1q2b2,且
b
i
大
于
等
于
0
,
小
于
与
等
于
n
i
b_i大于等于0,小于与等于n_i
bi大于等于0,小于与等于ni
对于下图中的约束,对应约束个数:
(来源参考)
约束个数中加一是考虑了还有1
完全平方数
若一个数能表示成某个整数的平方的形式,则称这个数为完全平方数。
例题
求解两个数最大公因数
如果暴力求解的话,即从一开始遍历,复杂度为 O ( m i n a , b ) O(min{a,b}) O(mina,b);而欧几里得算法(辗转相除法复杂度可化简为 O ( l o g n ) O(logn) O(logn))
定理
gcd(a,b)=gcd(b,r),其中r为a/b的余数
严格的逻辑证明这里省略,简单的说下思路:
假设gcd(a,b)=c, 那么假设
a
=
k
1
c
,
b
=
k
2
c
a=k_1c,b=k_2c
a=k1c,b=k2c,
a
/
b
=
k
…
r
a/b=k…r
a/b=k…r,则
r
=
a
−
b
k
=
k
1
c
−
b
k
2
c
=
(
k
1
−
b
k
2
)
c
r=a-bk=k_1c-bk_2c=(k_1-bk_2)c
r=a−bk=k1c−bk2c=(k1−bk2)c,即可以得到r也是c的倍数,还要说明c是r和b的最大公因数,
现在
b
=
k
2
c
b=k_2c
b=k2c,
r
=
(
k
1
−
b
k
2
)
c
r=(k_1-bk_2)c
r=(k1−bk2)c,需要证明
k
2
和
(
k
1
−
b
k
2
)
k_2和(k_1-bk_2)
k2和(k1−bk2)互质,可以用反证法,如果不互质的话,会存在一个比c大的公因数,矛盾,所以不成立。
代码
int gcd(int a,int b)
{
return b==0? a:gcd(b,a%b);
}
//b=0的时候,假如gcd(6,3),那么下一步递归得到gcd(3,0)即为3
补充性质
g
c
d
(
k
a
,
k
b
)
=
k
g
c
d
(
a
,
b
)
gcd(ka,kb)=k gcd(a,b)
gcd(ka,kb)=kgcd(a,b)
l
c
m
(
a
,
b
)
=
a
∗
b
g
c
d
(
a
,
b
)
lcm(a,b)=\frac{a*b}{gcd(a,b)}
lcm(a,b)=gcd(a,b)a∗b
素数判定
枚举是否含有除了1和本身的因数,其实只用枚举 2 − n 2-\sqrt{n} 2−n就行,因为如果不是素数,那么必存在 n = a b n=ab n=ab,用反证法可以证明必有一个数小于 n \sqrt{n} n;
代码
bool is_prime(int n)
{
if(x<=1)return false;
for(int i=2;i*i<=n;i++)
{
if(x%i!=0)return true;
}
return false;
}
多个素数判定
假如给你1-n,n个数,让你判断这n个数是不是素数,如果按照上述算法每个判断的话,复杂度为 O ( n n ) O(n\sqrt{n}) O(nn),下边引入一种筛法,可以将复杂度降为 O ( n l o g n ) O(nlogn) O(nlogn).
筛法:
一个非素数,肯定可以被它的约数整数,那枚举约数把约束的倍数标记,未被标记的数就是质数
代码
void is_primes(int n)
{
for(int i=2;i<=n;i++)
{
for(int j=i;j<=n/i;j++)
{
v[i*j]=1;
}
}
}
//复杂度计算:n(1+1/2+1/3+……)调和级数
代码优化-埃氏筛
刚刚那个会有很多重复标记,比如24,在i=2(212)的时候标记一次,在i=4(46)的时候标记一次
这次优化减少了部分重复计算
从质数的倍数都为合数的角度(刚刚是1-n所有数的倍数,由唯一分解定理可知,合数一定可以分解为素数的乘积),每次只标记素数的倍数,因为合数的话一定可以写为素数相乘的形式,在素数时被标记过一次,如果合数再标记一次就会重复。
void is_primes(int n)
{
for(int i=2;i<=n;i++)
{
//是不是素数,初始化均为素数0
if(v[i]) continue;
//是素数的话就开始标记
for(int j=i;j<=n/i;j++)
{
v[i*j]=1;
}
}
}
//复杂度:O(nloglogn)
欧拉筛(继续优化)
上边的方法还是会有重复,比如12,在i=4(3)、6(2)的时候都会被标记一次,所以可以每次用一个数最小的质因子去筛掉合数(用素数去筛合数)且只用最小质因子去一次就行(比如12,只用素数2筛一次也就是只在62的时候再筛(更大的倍数i乘以更小的素数计算出12),所以要达到内层循环在乘完42就break的效果)
void is_primes(int n)
{
for(int i=2;i<=n;i++)
{
if(!v[i]) primes[++cnt]=i; //,初始化为0代表全是素数,i是素数
for(int j=1;j<=cnt&&i*primes[j]<=n;j++)
{
v[i*primes[j]]=1; //所有质数的i倍,从最小倍开始,防止重复
if(i%primes[j]==0)break;
//如果i是prime的倍数的话
//每个合数只被它的最小质因子筛一次
//比如:12当i=12;prime=2(指的是prime是最小质因子而不是外层循环,外层循环代表的是倍数)的时候24才会被筛掉,所以i=8时对应prime=3会break掉(也就是i=8除以2为整数的时候)
// 整体的来讲,i是prime[j]的倍数时,i = kprime[j],若不break继续算 i乘以下一个,则i * prime[j+1] = prime[j] * k prime[j+1],这里prime[j]是最小的质因子,也就是算了两次,当i = k * prime[j+1]时会多重复了一次,所以才跳出循环。
}
}
}
//复杂度:O(n)
牛牛与LCM
(C++库中自带求最大公因子函数,可以直接拿来用)
#include<bits/stdc++.h>
using namespace std;
//若干个数,不是两个数
int lcm(int a,int b)
{
return a*b/__gcd(a,b);
}
int main()
{
int n;
cin>>n;
int *a=(int *)malloc(sizeof(int)*n);
for(int i=0;i<n;i++)
{
cin>>a[i];
}
int x;
cin>>x;
//这里要开long long
long long ans=1;
for(int i=0;i<n;i++)
{
//x是a[i]的倍数
//不仅要是倍数还要是最小公倍数
if(x%a[i]==0)
{
ans=lcm(ans,a[i]);
}
}
if(ans==x)
{
cout<<"Possible";
}
else
cout<<"Impossible";
return 0;
}
Sum of Consecutive Prime Numbers
这个题涉及两个知识点,求1~n的素数有哪些,上边已经讲解过欧拉可以达到线性复杂度;另一个点就是在一串从小到大的数组中,找到和为x的一段,起初看到的时候,基于现有的知识,首先想到的是前缀和,也就是说:先求出前缀和序列,然后借助两个指针表示所选取的序列,指针所指两个前缀和序列对应数做减法得到指针之间这段序列的和,通过移动指针来得到最终结果(指针的初始位置均指向第一个数,若所指段和小于x,则右指针右移,若大于则左指针右移)。
但是看完题解,学到了基于滑动窗口的方法做这个题,整体思路一样,借助两个指针,通过移动来找到最终结果,初始sum值即为第一个数,当右指针右移时sum加当左指针右移时sum减,sum与x比较,同样可以得到最终结果,并且省去了一个做前缀和的过程。
//题目给出了n的上限,且如果每次只按照当前n值找素数的话,会比较麻烦,所以直接一次性计算到上限
#include<bits/stdc++.h>
using namespace std;
int main()
{
//复习下,先不要看之前的笔记,需要维护一个素数数组和结果数组
int prime[int(4e7)+1];
bool is[int(4e7)+1];
//开两个int,空间会超,改成bool
int cnt=0;
memset(prime,0,sizeof(int)*(int(4e7)+1));
memset(is,0,sizeof(bool)*(int(4e7)+1));
for(int i=2;i<=int(4e7);i++)
{
if(!is[i])prime[++cnt]=i;
for(int j=1;j<=cnt&&i*prime[j]<=int(4e7);j++)
{
is[i*prime[j]]=1;
if(i%prime[j]==0)break;
}
}
int T;
cin>>T;
int flag=1;
while(flag<=T)
{
flag++;
int n;
cin>>n;
int l=1;
int r=1;
int ans=0;
int sum=prime[1];
for(;l>=1 && r<=cnt&&l<=r;)
{
if(sum>=n)
{
if(sum==n)ans++;
sum-=prime[l];
l++;
}
else if(sum<n)
{
//这里顺序不要反了啊
r++;
sum+=prime[r];
}
}
cout<<ans<<endl;
}
return 0;
}
Prime Distance(区间筛)
在素数判定中有提到,只用循环至
n
\sqrt n
n,那么同理,一个数n的最小质因子一定小于等于
n
\sqrt n
n(同样反证法),也就是说,我们仅需要知道2~
n
\sqrt n
n的素数,就可以筛选1~n
这个题一定要记得long long!!
//这个题目和上个题目不一样之处在于,开不出来2的31次方的数组,并且筛到2的31次方的话即使是线性复杂度也会超时
//所以要做区间筛
#include <bits/stdc++.h>
using namespace std;
const int N=1e6+10;
//只筛选出该范围内的素数就行
const int range=46341+10;
int prime[range];
bool is[range];
int p[N];
int max(long long a,long long b)
{
return a>b?a:b;
}
int main()
{
int cnt=0;
memset(is,0,sizeof(int)*(range));
for(int i=2;i<=range-10;i++)
{
if(!is[i])prime[++cnt]=i;
for(int j=1;j<=cnt&&i*prime[j]<=range-10;j++)
{
is[i*prime[j]]=1;
if(i%prime[j]==0)break;
}
}
int T;
cin>>T;
long long r;
long long l;
while(T--)
{
memset(p,0,sizeof(int)*(N));
cin>>l>>r;
//筛区间的时候用埃氏筛,欧拉筛会超时
//因为直接按照倍数标记就行了,欧拉的话还要循环至质因数最小的那个倍数
/*
for(int i=max(2,l/prime[cnt]);i<=r/2+1;i++)
{
for(int j=1;j<=cnt&&i*prime[j]>=l&&i*prime[j]<=r;j++)
{
p[i*prime[j]-l]=1;
if(i%prime[j]==0)break;
}
}*/
for (long long i = 1; i <= cnt && prime[i] <= r; i++) {
long long w = (l + prime[i] - 1) / prime[i];//第一个数是i的多少倍
for (long long j = max(2, w); prime[i] * j <= r; j++)
p[prime[i] * j - l] = 1;
}
vector<long long int > pri;
for (long long i = max(2, l); i <= r; i++) {
if (!p[i - l]) pri.push_back(i);
p[i - l] = 0;
}
/*
for(int i=0;i<N;i++)
{
if(i+l>r)break;
if(p[i]==0)pri.push_back(i);
}*/
if(pri.size()<=1){
cout<<"There are no adjacent primes."<<endl;
continue;
}
else{
pair<long long,long long> min={0x3f3f3f3f3f,-1},max={-1,-1};
for(long long i=0;i<pri.size()-1;i++)
{
if(pri[i+1]-pri[i]>max.first){
max.first=pri[i+1]-pri[i];
max.second=i;
}
if(pri[i+1]-pri[i]<min.first){
min.first=pri[i+1]-pri[i];
min.second=i;
}
}
cout<<pri[min.second]<<','<<pri[min.second+1]<<" are closest, "
<<pri[max.second]<<','<<pri[max.second+1]<<" are most distant."
<<endl;
continue;
}
}
return 0;
}
实现唯一分解定理
就是从最小的素数开始一个一个除
for(int i=1;i<=cnt&&i<sqrt(n);i++)
{
while(n%prime[i]==0)
{
a[c++]=prime[i];
n=n%prime[i];
}
}
X-factor Chains
题中要求序列尽可能地长,也就是所有
a
i
+
1
=
t
∗
a
i
a_{i+1}=t*a_i
ai+1=t∗ai中的t都要尽量的小,换个思路,质数是不能再分的,也就是说如果4*a可以写作
2
∗
2
∗
a
2*2*a
2∗2∗a从而达到t最小的效果,所以这道题就是实现唯一分解定理,然后对素数计算全排列