算法笔记:数学问题+数论初步一(未完待续)

数学问题+数论初步一

一、辗转相除法(欧几里得算法)
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;
}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值