数学问题+数论初步一
一、辗转相除法(欧几里得算法)
1. 求两个数的gcd和lcm
法一:非递归解法
#include <stdio.h>
void Swap(int *a,int *b)
{
int t;
t=*a;
*a=*b;
*b=t;
}
int getGcd(int a,int b) //求a和b的最大公约数
{
int m;
if(a<b) //进入辗转相除过程前,保证a>=b
Swap(&a,&b);
while(b!=0) //b不为0时循环
{
m=a%b; //每次循环求出a%b,赋给m
a=b; //然后用b更新a
b=m; //用a更新m
}
return a; //当b为0跳出循环时,a就是所求
}
int getLcm(int a,int b) //求a和b的最小公倍数
{
return a*b/getGcd(a,b);
}
int main()
{
int a,b;
int gcd,lcm;
scanf("%d %d",&a,&b);
gcd=getGcd(a,b);
lcm=getLcm(a,b);
printf("%d %d\n",gcd,lcm);
return 0;
}
法二:递归解法
#include <stdio.h>
int getGcd(int a,int b) //求a和b的最大公约数
{
if(b==0)
return a;
return getGcd(b,a%b);
}
int getLcm(int a,int b) //求a和b的最小公倍数
{
return a*b/getGcd(a,b);
}
int main()
{
int a,b;
int gcd,lcm;
scanf("%d %d",&a,&b);
gcd=getGcd(a,b);
lcm=getLcm(a,b);
printf("%d %d\n",gcd,lcm);
return 0;
}
2. 应用:线段上格点的个数
给定平面上的两个格点P1=(x1, y1), P2(x2, y2),线段P1P2上除P1和P2意外一共有几个格点?(-10^9<=x1, x2, y1, y2<=10^9,格点是指横纵坐标均为整数的点)
法一:检查所有满足min(x1, x2)<=x<=max(x1, x2) 且 min(y1, y2)<=y<=max(y1, y2)的格点。可得到正确答案,但复杂度太高(O(|x1-x2|*|y1-y2|),不适用于坐标绝对值较大的情况)
法二:结合辗转相除法的思想,结果是|x1-x2|和|y1-y2|的gcd-1
#include <stdio.h>
#include <math.h>
void Swap(int *a,int *b)
{
int t;
t=*a;
*a=*b;
*b=t;
}
int getGcd(int a,int b) //求a和b的最大公约数
{
if(b==0)
return a;
return getGcd(b,a%b);
}
int main()
{
int x1,x2,y1,y2;
int x,y,ans;
scanf("%d %d %d %d",&x1,&y1,&x2,&y2);
x=fabs(x2-x1);
y=fabs(x2-x1);
if(x==0 && y==0) //特殊情况:两个格点重合
ans=0;
else //其它情况:线段上的格点数=|x1-x2|和|y1-y2|的gcd-1
ans=getGcd(x,y)-1;
printf("%d\n",ans);
return 0;
}
二、扩展欧几里得算法
三、素数基础算法
1. 素数判定
给定整数n(1<=n<=10^9),判断n是否是素数。若是则输出"Yes",否则输出"No"
分析:对一个整数是否是素数的判定,通常使用O(根号n)的算法即可。即检查2~根号n的所有整数k,判断n能否整除k
#include <stdio.h>
#include <math.h>
int is_Prime(int n)
{
int i;
if(n<2)
return 0;
for(i=2;i<=sqrt(n);i++) //或写作 for(i=2;i*i<=n;i++)
{
if(n%i==0)
return 0;
}
return 1;
}
int main()
{
int n;
while(scanf("%d",&n)!=EOF)
{
if(is_Prime(n))
printf("Yes\n");
else
printf("No\n");
}
return 0;
}
2. 求素数个数
给定整数n(n<=10^6),求n以内素数的个数
分析:对多个整数是否是素数的判断,应改进算法性能。这里使用埃氏筛法
#include <stdio.h>
#include <math.h>
const int maxn=1000005;
int is_prime[maxn];//记录数i是否是素数 1-是 0-否
int prime[maxn]; //保存求出的素数
int Sieve(int n) //求n以内素数的个数
{
int i,j;
int ans=0;
for(i=0;i<=n;i++)
is_prime[i]=1;
is_prime[0]=is_prime[1]=0; //0和1不是素数
for(i=2;i<=n;i++) //从2开始,判断i是否是素数
{
if(is_prime[i]) //若发现i是素数
{
prime[ans++]=i; //将其保存到prime数组中
for(j=2*i;j<=n;j+=i)//去除所有i的倍数
is_prime[j]=0;
}
}
return ans;
}
int main()
{
int i,n;
scanf("%d",&n);
printf("Ans=%d\n",Sieve(n));
for(i=0;i<Sieve(n);i++) //输出素数
printf("%d ",prime[i]);
printf("\n");
return 0;
}
3. 求区间素数个数
给定整数a和b(a<b<10^12, b-a<=10^6),求区间[a, b)内的素数个数
#include <iostream>
using namespace std;
typedef long long ll;
ll a,b;
ll ans;
const int maxn=1000005;
const int maxsqrtb=1000005;
bool is_prime[maxn];
bool is_prime_small[maxsqrtb];
void segment_sieve(ll a,ll b)
{
ll i,j;
for(i=0;i*i<b;i++)
is_prime_small[i]=true;
for(i=0;i<b-a;i++)
is_prime[i]=true;
for(i=2;i*i<=b;i++)
{
if(is_prime_small[i])
{
for(j=2*i;j*j<=b;j+=i) //筛[2,根号b)
is_prime_small[i]=false;
for(j=max(2LL,(a+i-1)/i)*i;j<b;j+=i) //筛[a,b)
is_prime[j-a]=false;
}
}
}
int main()
{
ll i;
cin>>a>>b;
ans=0;
segment_sieve(a,b);
for(i=0;i<b-a;i++)
{
if(is_prime[i]==true)
{
//cout<<i+a<<" "; //输出素数
ans++;
}
}
//cout<<endl;
cout<<ans<<endl;
return 0;
}
四、模运算
1. 大整数取模
输入正整数n和m,输出 (n mod m) 的值(n<=10^100, m<=10^9)
#include <stdio.h>
#include <string.h>
#define maxn 65005
typedef long long ll;
char n[105];
ll m;
void getmod(char *n)
{
ll i;
ll len=strlen(n);
ll ans=0; //ans记录求得的模
for(i=0;i<len;i++) //把"大整数"写成"自左向右"的形式,单步取模
ans=((ans*10+n[i]-'0')%m);
printf("%lld\n",ans);
}
int main()
{
scanf("%s %d",n,&m);
getmod(n);
return 0;
}
2. 幂取模
输入正整数a, n和m,输出a^n mod m的值,其中a, n, m<=10^9
#include <stdio.h>
int a,n,m;
int getPowmod(int a,int n,int m) //采用分治法,a^n=(a^(n/2))^2,若n为奇数结果再乘a
{
int x;
long long ans;
if(n==0)
return 1;
x=getPowmod(a,n/2,m);
ans=(long long)x*x%m;
if(n%2==1)
ans=ans*a%m;
return ans;
}
int main()
{
scanf("%d %d %d",&a,&n,&m);
printf("%d\n",getPowmod(a,n,m));
return 0;
}
五、快速幂运算(以“反复平方法”为例)
我们把对任意的1<x<n都有x^n=x mod n成立的合数n称为Carmichael number,给定一个整数n,判断它是不是Carmichael number。(2<n<65000)
法一:非递归解法
#include <stdio.h>
#include <math.h>
#define maxn 65005
typedef long long ll;
int n;
int prime[maxn];
bool is_Prime(ll n) //判断数n是否是素数
{
ll i;
for(i=2;i<=sqrt(n);i++)
{
if(n%i==0)
return false;
}
return true;
}
ll mod_pow(ll x,ll n,ll mod)
{
ll res=1;
while(n>0)
{
if(n&1) //若n为奇数(即二进制最低位为1),则乘上x^(2^i)
res=res*x%mod;
x=x*x%mod; //将x平方
n>>=1; //n=n/2
}
return res;
}
int main()
{
ll i;
scanf("%lld",&n);
if(is_Prime(n)) //若n是素数,则不合题意
printf("No\n");
else //否则枚举x(2~n-1),判断(x^n)%n 和 x%n是否相等
{
for(i=2;i<n;i++)
{
if(mod_pow(i,n,n)!=i) //如果出现不等的情况,立即退出循环
break;
}
if(i==n) //对任意的x均有(x^n)%n 和 x%n相等,符合题意
printf("Yes\n");
else //否则不合题意
printf("No\n");
}
return 0;
}
法二:递归解法
#include <stdio.h>
#include <math.h>
#define maxn 65005
typedef long long ll;
int n;
int prime[maxn];
bool is_Prime(ll n) //判断数n是否是素数
{
ll i;
for(i=2;i<=sqrt(n);i++)
{
if(n%i==0)
return false;
}
return true;
}
ll mod_pow(ll x,ll n,ll mod)
{
if(n==0)
return 1;
ll res=mod_pow(x*x%mod,n/2,mod);
if(n&1)
res=res*x%mod;
return res;
}
int main()
{
ll i;
scanf("%lld",&n);
if(is_Prime(n)) //若n是素数,则不合题意
printf("No\n");
else //否则枚举x(2~n-1),判断(x^n)%n 和 x%n是否相等
{
for(i=2;i<n;i++)
{
if(mod_pow(i,n,n)!=i) //如果出现不等的情况,立即退出循环
break;
}
if(i==n) //对任意的x均有(x^n)%n 和 x%n相等,符合题意
printf("Yes\n");
else //否则不合题意
printf("No\n");
}
return 0;
}