数学(三):gcd,lcm,一次函数与素数筛
引言:
函数与数论是数学学科中的主要模块,也是信息学奥赛考察的重点内容。本文将介绍有关数论与一次函数的相关内容。
在进入正题以前,让我们来看一道有关质因数分解的题目。
附加题:P1069 [NOIP2009 普及组] 细胞分裂
根据题目要求,只有培养后细菌的总数能被试管数M整除才能完成培养,而
代码如下:
/*
一个整数能被M整除,说明能平均分
最后的细胞总数量是M的倍数(细胞总数%M==0)
0s:s[i];1s:s[i]^2;2s:s[i]^3;3s:s[i]^4;...
s[i]^?|M
(p1^k1*p2^k2*...*pm^km)^?=s[i]^?
=>p1^(k1*?)*p2^(k2*?)*...*pm^(km*?)
M=q1^f1*q2^f2*...*qw^fw
添加幂次,不修改底数(质因子的数量)
如果s[i]^?能被M整除
1.M的所有质因子全部在s[i]中出现过,s[i]能把M所有的质因子整除
2.默认满足:M的对应质因子幂次小于 s[i]的质因子幂次
*/
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+100;
struct nums{
int prime;//质因子
int cnt; //个数
}p[N];
int a[N];
int m1,m2;//底数,幂次
int n;
int total=0;
void div(int num){
if(num==1){
return;//特判
}
for(int i=2;i*i<=num;i++){//O(sqrt(n))
if(num%i==0){
p[++total].prime=i;//存储当前的质因子
while(num%i==0){
num/=i;
p[total].cnt++;
}
p[total].cnt*=m2;//默认需要乘m2
}
}
if(num!=1){
p[++total].prime=num;
p[total].cnt=m2;
}
return;
}
bool check(int x){
for(int i=1;i<=total;i++){
if((x%p[i].prime)) return 0;
}
return 1;
}
int minn=INT_MAX;
/*
第0s,都是1个
第1s,p1这个质因子幂次为tmp
第2s,p1这个质因子的幂次为2*tmp
(tmp>=p[i].cnt)
*/
void cultivate(int x){
int maxx=0;//基础培育时间最小值是0
for(int i=1;i<=total;i++){//m1质因子的种类
int tmp=0;
while(x%p[i].prime==0){
tmp++;
x/=p[i].prime;
}
int ans=(p[i].cnt-1)/tmp+1;//向上取整
maxx=max(maxx,ans);
}
minn=min(minn,maxx);
return;
}
signed main(){
cin>>n;
cin>>m1>>m2;
div(m1);//分解质因数
for(int i=1;i<=n;i++){
cin>>a[i];
if(check(a[i])==0){//检查一开始是否就不存在对应的质因子
continue;
}
cultivate(a[i]);//能继续说明值得继续培养
}
if(minn==INT_MAX) cout<<-1;
else cout<<minn;
return 0;
}
第一部分:gcd和lcm
gcd(最大公约数)与lcm(最小公倍数)是信息学奥赛中的主要内容之一。两者存在以下关系:
我们可以简单地举例验证一下。例如,当a=4,b=6时,易得两者的最小公倍数为12。我们先求出gcd(4,6)=2,则
推导过程如下图所示:
我们还能将一次函数y=kx+b看作一个等差数列,其中b为首项,k为公差,x为项数。
下面来看一道例题。
例题:P2026 求一次函数解析式
本题是典型的求函数的解析式问题,可以直接利用上文求斜率和截距的公式进行求解。注意:当求k或b时答案非整数时,应当将分子和分母同时除以它们的最大公约数,以达到约分的目的。注意分情况输出解析式。 代码如下:
#include<bits/stdc++.h>
using namespace std;
int x1,yy,x2,y2;
int k,b;
int gcd(int a,int b){
return b?gcd(b,a%b):a;
}
int main(){
cin>>x1>>yy>>x2>>y2;
cout<<"y=";
int xt=x1-x2,yt=yy-y2;
if((double)yt/xt<0){
cout<<"-";
xt=abs(xt);yt=abs(yt);
}
if(yt%xt!=0){
int t=gcd(xt,yt);
xt/=t,yt/=t;
cout<<yt<<"/"<<xt<<"*";
}
else cout<<yt/xt;
cout<<"x";
int xb=yy*x2-x1*y2;
int yb=x2-x1;
if(xb==0) return 0;
if((double)xb/yb<0){
cout<<"-";
xb=abs(xb);yb=abs(yb);
}
else cout<<"+";
if(xb%yb==0){
cout<<xb/yb;
}
else{
int tt=gcd(xb,yb);
xb/=tt,yb/=tt;
cout<<xb<<"/"<<yb;
}
return 0;
}
第三部分:埃氏素数筛法
对于素数筛,我们有如下的朴素方法:
bool prime(int x){
if(x<=1)return false;
for(int i=2;i*i<=x;i++){
if(x%i==0) return false;
}
return true;
}
对于求一个数是否为素数,上述方法的复杂度为
接下来介绍一种效率较高的筛法:埃氏筛法。其基本原理是:当我们找到一个素数,其所有的倍数都是合数。例如,2是质数,而4,6,8,10,……是2的倍数,并且它们都是合数。因此,埃氏筛法在找到一个素数(即没有做标记的数)时会将其所有的倍数(均为合数)做标记,避免重复筛选。其算法复杂度为
算法实现如下:
bool vis[N];
int prime[N];
void E(){
vis[0]=vis[1]=1;
for(int i=1;i<=N;i++){
if(!vis[i]){
for(int j=i*2;j<=N;j+=i)vis[j]=true;
}
}
return;
}
下面来看两道例题。
例题1:B3969 [GESP202403 五级] B-smooth 数
本题要求求出给定数的最大质因子。我们可以对埃氏筛法进行改进。每当我们发现一个素数,便将它和它的倍数的最大质因子标记为它本身。在往后搜查的过程中,每当一个数发现了比当前质因子更大的质因子时,原质因子将被新值覆盖。由于质数以单调递增的顺序排列,当我们筛取完毕时对每一个数存下的质因子便是其最大的质因子。
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int N=1e6;
int vis[N+10];
int prime[N+10],id=1;
void E(){
vis[0]=vis[1]=1;//特判0,1都不是
for(int i=2;i<=N;i++){
if(!vis[i]){//合数的所有倍数一定是该合数的质因子的倍数
for(int j=i;j<=N;j+=i){
vis[j]=i;
}
}
}
}
int n,b;
int main(){
cin>>n>>b;
E();
int ans=0;
for(int i=2;i<=n;i++){
if(vis[i]<=b) ans++;
}
cout<<ans+1;
return 0;
}
例题2:P6421 [COCI2008-2009#2] RESETO
本题要求出被删除的第k个数。基于埃氏筛法,我们可以在标记的过程中累加计数器。当计数器的值为k时输出对应数即可。
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int N=1e3+100;
bool vis[N+50];
int cnt=0;
int n,k;
void E(){
vis[0]=vis[1]=1;
for(int i=2;i<=n;i++){
if(!vis[i]){
cnt++;
if(cnt==k){
cout<<i<<endl;
exit(0);
}
for(int j=i+i;j<=n;j+=i){
if(!vis[j]){
cnt++;
if(cnt==k){
cout<<j<<endl;
exit(0);
}
}
vis[j]=1;
}
}
}
}
int main(){
cin>>n>>k;
E();
return 0;
}
结语:
本文介绍的相关数学知识都是该学科的基础性内容,也是信息学中的重要内容。对于每一位竞赛选手,这些都是必须熟练掌握的知识内容。