数论(取模、快速幂、欧几里德算法、扩展欧几里得算法、素数筛)+例题(A/B、青蛙的约会、美素数)

本文介绍了数论中的基本概念,如取模运算的性质、快速幂算法用于高效计算幂次模特定数,以及欧几里德算法(GCD)和扩展欧几里德求解同余方程。此外,文章详细讲解了埃氏筛和欧拉筛这两种素数筛选方法,以及如何解决实际问题中的A/B青蛙约会难题。通过实例演示,读者将掌握这些核心算法在编程中的应用。
摘要由CSDN通过智能技术生成

数论

取模

有关取模的几个重要式子:

1.(ab)%c = ( (a%c)(b%c) )%c

2.(b/a)%c = b*(a)^(c-2)%c

快速幂

快速幂这个算法是个死算法,理解代码后把他记下来。

快速幂:

int POW(int a,int b){
    int ans = 1;
    int base = a;
    while(b){
        if(b & 1) ans *= base;
        base *= base;
        b >>= 1;
    }
    return ans;
}

快速幂(取模):

int pow_mod(int a,int b,int c){
    int ans = 1;
    int base = a%c;
    while(b){
        if(b & 1) ans = (ans*base)%c;
        base = (base*base)%c;
        b >>= 1;
    }
    return ans;
}

GCD/欧几里德算法

gcd即最大公因数,求最大公因数,较为简单的方法辗转相除法, 又名欧几里德算法。用较大数除以较小数,再用出现的余数(第一余数)去除除数,再用出现的余数(第二余数)去除第一余数,如此反复,直到最后余数是0为止。如果是求两个数的最大公约数,那么最后的除数就是这两个数的最大公约数。

long long gcd(long long a,long long b){
	if(b == 0) return a;
	else return gcd(b,a%b);
} 

扩展欧几里德

讲的很好的一篇文章

对于两个不全为0的整数a、b,必存在一组解x,y,使得ax+by==gcd(a,b);---------1式

代码实现


int exgcd(int a,int b,int &x,int &y)
{
    if(b==0)
    {
        x=1;y=0;
        return a;
    }
    int r=exgcd(b,a%b,x,y);
    int temp=y;
    y=x-(a/b)*y;
    x=temp;
    return r;
} 

由此拓展开ax+by==c,它的解应该是什么呢?x1=x(c/gcd(a,b)),y1=y(c/gcd(a,b))。(其中x,y为1式中解得的x与y)

这个式子的通解x = x1+t*b/d,

​ y = y1- t*a/d。(t为任意实数)

素数筛

参考

埃氏筛

​ 做法其实很简单,首先将2到n范围内的整数写下来,其中2是最小的素数。将表中所有的2的倍数划去,表中剩下的最小的数字就是3,他不能被更小的数整除,所以3是素数。再将表中所有的3的倍数划去……以此类推,如果表中剩余的最小的数是m,那么m就是素数。然后将表中所有m的倍数划去,像这样反复操作,就能依次枚举n以内的素数,这样的时间复杂度是O(nloglogn)。

代码实现:

bool vis[maxn] = {0};//数值为0时代表是素数
int prime[maxn] = {0},x = 0;
void isprime(int n) 
{
    
    for(int i=2;i<=n;i++)
    {
        if(!vis[i]) prime[x++]=i;
        for(int j=2;j*i<=n;j++)
        {
            vis[i*j]=true;
        }
    }
}

欧拉筛

在埃氏筛法的基础上,让每个合数只被它的最小质因子筛选一次,以达到不重复的目的。其他复杂度(O(n)).

代码实现

bool vis[maxn] = {0};//数值为0时代表是素数
int prime[maxn] = {0},x = 0;
void oulasai(int n)  //欧拉筛
{
    for(int i=2;i<=n;i++)
    {
        if(!vis[i]) prime[x++]=i;
        for(int j=0;j<x;j++)
        {
            if(i*prime[j]>n) break;
            vis[i*prime[j]]=true;
            if(i%prime[j]==0) break;
        }
    }
}

例题

A/B

要求(A/B)%9973,但由于A很大,我们只给出n(n=A%9973)(我们给定的A必能被B整除,且gcd(B,9973) = 1)。

Input

数据的第一行是一个T,表示有T组数据。
每组数据有两个数n(0 <= n < 9973)和B(1 <= B <= 10^9)。

Output

对应每组数据输出(A/B)%9973。

Sample Input

2
1000 53
87 123456789

Sample Output

7922
6060

解:

想要写出这一题一定要知道这几个公式,(ab)%m = ( (a%m)(b%m) )%m,(a/b)%m = a*b^(m-2)%m;

所以题目中(A/B)%9973 = A*B^(9971)%9973 = ( (A%9973)(B^(9971)%9973) )%9973 = ( n(B^(9971)%9973) )%9973;

这里需要对B(9971)取模,如果把B(9971)这个先算出来显然不行,会爆,所以就要用到幂取模的算法,这里我使用快速幂取模。

ac代码

#include<cstdio>
#include<iostream>
using namespace std;
int powmod(int a,int b,int c){//快速幂取模
    int ans = 1;
    int base = a%c;
    while(b){
        if(b & 1) ans = (ans*base)%c;
        base = (base*base)%c;
        b >>= 1;
    }
    return ans;
}
int main(){
	int t,n,b,ans;
	scanf("%d",&t);
	while(t--){
		scanf("%d %d",&n,&b);
		ans = (n*powmod(b,9971,9973) )%9973;
		printf("%d\n",ans);
	}
	return 0;
}

青蛙的约会

题目链接

两只青蛙在网上相识了,它们聊得很开心,于是觉得很有必要见一面。它们很高兴地发现它们住在同一条纬度线上,于是它们约定各自朝西跳,直到碰面为止。可是它们出发之前忘记了一件很重要的事情,既没有问清楚对方的特征,也没有约定见面的具体位置。不过青蛙们都是很乐观的,它们觉得只要一直朝着某个方向跳下去,总能碰到对方的。但是除非这两只青蛙在同一时间跳到同一点上,不然是永远都不可能碰面的。为了帮助这两只乐观的青蛙,你被要求写一个程序来判断这两只青蛙是否能够碰面,会在什么时候碰面。
我们把这两只青蛙分别叫做青蛙A和青蛙B,并且规定纬度线上东经0度处为原点,由东往西为正方向,单位长度1米,这样我们就得到了一条首尾相接的数轴。设青蛙A的出发点坐标是x,青蛙B的出发点坐标是y。青蛙A一次能跳m米,青蛙B一次能跳n米,两只青蛙跳一次所花费的时间相同。纬度线总长L米。现在要你求出它们跳了几次以后才会碰面。

Input

输入只包括一行5个整数x,y,m,n,L,其中x≠y < 2000000000,0 < m、n < 2000000000,0 < L < 2100000000。

Output

输出碰面所需要的跳跃次数,如果永远不可能碰面则输出一行"Impossible"

Sample Input

1 2 3 4 5

Sample Output

4

解:

我们首先可以假设经过t后相遇,如果他们相遇则会有式子(x+mt)-(y+nt) = kl成立,对该式子进行转换可得(n-m)t+lk = x-y;

这就刚刚好符合扩展欧几里得的式子ax+by = c。

ac代码

#include<cstdio>
#include<iostream>
using namespace std;
long long n,m,x,y,l,xx,yy;
long long a,b,c,d,t;
long long exgcd(long long a, long long b, long long &xx, long long &yy) {
    if (b == 0) {
        xx = 1;
        yy = 0;
        return a;
    }
    long long r = exgcd(b, a%b, xx, yy);
    long long t = xx; xx = yy; yy = t - a/b*yy;
    return r;
}
int main(){
	//long long n,m,x,y,l;
	
	scanf("%lld %lld %lld %lld %lld",&x,&y,&m,&n,&l);
	c = x - y;
	a = n - m;
	b = l;
	d = exgcd(a,b,xx,yy);
	//printf("%lld %lld\n",xx,yy);
	if(c%d!=0){
		printf("Impossible\n");
		return 0;
	}
	xx = xx*(c/d);yy = yy*(c/d);
	t = xx*d/b;//x = x1+t*b/d,因为t为任意实数所以令t = -t,令x等于0,解出t = x1*d/b。
	xx = xx - t*b/d;
	if(xx<0) xx+=(b/d);x//xx只能是正数。
	printf("%lld",xx);
	return 0;
}

美素数

小明对数的研究比较热爱,一谈到数,脑子里就涌现出好多数的问题,今天,小明想考考你对素数的认识。
  问题是这样的:一个十进制数,如果是素数,而且它的各位数字和也是素数,则称之为“美素数”,如29,本身是素数,而且2+9 = 11也是素数,所以它是美素数。
  给定一个区间,你能计算出这个区间内有多少个美素数吗?

Input

第一行输入一个正整数T,表示总共有T组数据(T <= 10000)。
接下来共T行,每行输入两个整数L,R(1<= L <= R <= 1000000),表示区间的左值和右值。

Output

对于每组数据,先输出Case数,然后输出区间内美素数的个数(包括端点值L,R)。
每组数据占一行,具体输出格式参见样例。

Sample Input

3
1 100
2 2
3 19

Sample Output

Case #1: 14
Case #2: 1
Case #3: 4

解:

按照题目的意思,先用筛法把素数筛出来,随后进行计算个数。但是我们发现这样子会超时,所以我们还需要做一个预处理,用数组s[i],来存放答案,s[i]表示1-i中美素数的个数所以我们知道答案便是s[r]-s[l-1]。

ac代码

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
#define maxn 1000000
bool vis[maxn+5] = {0};
int prime[maxn+5] = {0},x = 0;
int s[maxn+5] = {0};
void osai(int n){//欧拉筛
	int i,j;
	for(i = 2;i<=n;i++){
		if(!vis[i]) prime[x++] = i;
		for(j = 0;j<x;j++){
			if(i*prime[j]>n) break;
			vis[i*prime[j]] = 1;
			if(i%prime[j] == 0) break;
		}
	}
}

int change(int n){//求各位数之和
	int sum = 0;
	while(n){
		sum+=n%10;
		n/=10;
	}
	return sum;
}

int main(){
	int t,l,r,count,i,j;
	osai(1000003);
	vis[1] = 1;
	for(i = 1;i<=1000000;i++){//预处理
		s[i] = s[i-1];
		if(vis[i] == 0&&vis[change(i)] == 0) s[i]++;
	}
	scanf("%d",&t);
	for(j = 1;j<=t;j++){
		count = 0;
		scanf("%d %d",&l,&r);
		printf("Case #%d: %d\n",j,s[r]-s[l-1]);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值