数论

数论
数论部分于我的理解就是将数学与计算机的运算相适应,有些部分求解过程会变,因为计算机不能和人一样进行一些看起来简单的运算,但是却可以完成许多人无法完成的操作。总的数学思想不变,利用已有的数学知识去完成题目。
1.快速幂方法。一次乘一个底太慢了,这个利用了类似于二分的思想,只要求出了次方的一半,将这个直接平方就可以得到想要的答案。
代码如下:

long long Mode(long long a, long long b)
{
	long long sum = 1;
	
	while (b > 0) 
	{
		if (b % 2 == 1||b&1)//这两者是一样的	
		//判断是否是奇数,是奇数的话将多出来的数事先乘如sum
		sum = (sum * a) % mod;
 
		b /= 2;
		a = (a * a) % mod;// 不断的两两合并再取模,减小a和b的规模
	}
	return sum;
}

2.求最大公因子。利用辗转相除法进行。这个也很好证明。在这里插入图片描述
代码如下:

//递归求解
int gcd(int a,int b)
{
	return b ? gcd(b,a%b):a;
}
//非递归
int gcd(int a,int b) 
{
    int r;
    while(b>0) 
    {
        r=a%b;
        a=b;
        b=r;
    }
    return a;
}

3.拓展欧几里得。
这个用于求线性方程。求类似于ax+by=c的x,y的解。容易得到,当c%mod(gcd(a,b))=0才会有解。
由最大公因数的定义,可知a是gcd(a,b)的倍数,且b是gcd(a,b)的倍数,若 x,y都是整数就确定了ax + by是gcd(a,b)的倍数,因为c = ax + by,所以 c 必须是 gcd(a,b)的倍数。

以下来自洛谷“学委”大佬的题解
链接https://www.luogu.com.cn/problem/solution/P1082.
我们拿到了一组 a,b设 G = gcd(a, b)。那么目标是求出满足
ax + by = G(①)
的整数 x 与 y。其中x,y 应当是满足条件的最小正整数.
如果我们先前已经求出了另一组数 x2, y2 ,它们满足这么一个式子:
bx2 + (a % b)y2 = G(②),
则此时结合①②一定有:
ax + by = bx2 + (a % b)y2 (③)
可见,在这个“如果”实现的时候,我们的目标变成了“求出满足上式的 x 和 y”。
其中 a,b,x2,y2 都已知,x,y 待求。因为未知数比方程更多,所以没有唯一解。我们先求出一组必然存在的解,最后将在“答案处理”时转为最小解。

怎么求呢?取模运算是 a % b = a - b×(a/b),所以方程③实际上是:
ax + by = bx2 + ( a - b × (a/b) ) y2
⇒ax + by = bx2 + ay2 −b × (a/b) y 2
⇒ax + by =ay2 +b( x2 − (a/b) y2 )
看上面这个方程,一组必然存在的解出现了:
x = y2, y = x2 - (a/b) y2(④)
可见,我们只要求出 x2,y2,就能得出正确的 x,y。问题是 x2,y2 怎么求。
现在我们手上是 b,a% b这两个系数,而目标是求出 x2 和 y2满足:
bx2 + (a % b) y2 = G(②)
把①和②对比一下:
ax + by = G(①)
原方程中的系数 a 变成了②中的系数 b,原方程中的系数 b 变成了②中的 a% b 而已。
所以,把新的方程也看作 ax + by = G的形式(只是系数 a 和 b 的具体数值改变了)。然后按照上面的一模一样下来(其实都只是推导过程),我们发现,最好有 x3,y3来支撑 x2, y2 。
再一模一样下来,我们又需要 x4,y4来支撑 x3, y3。
……
这个递归中 a, ba,b 不断被替代为 b, a % b,这个替换方式与普通欧几里得是一样的,所以最后会出现 an = G, bn = 0。
这时要直接返回了,我们需要一组 xn,yn满足
an xn + bn yn = G =G(⑤)。
然而该层的 an = G, bn = 0。所以只要⑤左边取 xn = 1,这个方程就妥妥的成立了。
(最后一层的 yn 建议取 0。然而由于 b = 0,就算返回其它数值,方程也一定成立。但这样的程序容易出错,因为 y_n在回溯时滚雪球式增长,容易数值越界。)
最后一层结束后,就开始返回,直到最上层。每一层可以轻松地根据下层的 x(k+1),y({k+1)求出当前层的 xk, yk。
整个过程就是:以辗转相除的方式向下递进,不断缩小系数,保证会出现有确定解的最后一层。
代码如下:

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

4.乘法逆元。
在计算机中,当求(a/b)%mod的时候,因为除法没办法想加减乘一样直接将里面拆开%mod,这时候就需要利用b的逆元去求解问题。假设b的逆元为c,有(a%b)%mod=(ac)%mod=((a%mod)(b%mod))%mod。
关于求解乘法逆元可以使用拓展欧几里得,求解bx=1(%mod)的x解即可,或者使用费马定理求解。费马定理的证明如下。
在这里插入图片描述

5.素数筛。
埃筛代码:效率低但是容易理解。

ll isprime(ll n)
{
	ll m=(ll)sqrt(n);
	for(ll i=3;i<=m;i+=2)
	{
		if(!vis[i])
		for(ll j=i*i;j<=n;j+=2*i)/*i*(2*i-1)在 2*i-1时都已经被筛去,
		所以从i*i开始,j为奇数,i也是奇数.这里只需要判断奇数所以+2i */
			vis[j]=1;
	}
	ll c=0;
	prime[0]=2;
	for(ll i=3;i<=n;i+=2)
		if(!vis[i])
			prime[++c]=i;
	return c;
}

欧拉筛:线性筛。

ll isprime(ll end)
{
	for(int i=2;i<=end;i++)
	{
		if(!vis[i])
		prime[++prime[0]]=i;//存储质数 
		for(int j=1;j<=prime[0]&&i*prime[j]<=end;j++)
		{
			vis[i*prime[j]]=1;
			if(i%prime[j]==0)//防止多次被筛 
			break;	
/*当 i是prime[j]的倍数时,i = kprime[j],如果继续运算
 j+1,i * prime[j+1] = prime[j] * k prime[j+1],
 这里prime[j]是最小的素因子,当i = k * prime[j+1]时会重复,
 所以才跳出循环。
举个例子 :i = 8 ,j = 1,prime[j] = 2,如果不跳出循环,
prime[j+1] = 3,8 * 3 = 2 * 4 * 3 = 2 * 12,
在i = 12时会计算。因为欧拉筛法的原理便是通过最小素因子来消除。*/
		}
	}
//	cout<<prime[0]<<endl;
//	for(int i=1;i<=prime[0];i++)
//	cout<<prime[prime[0]]<<" ";
	cout<<prime[0]<<endl;
}

题解
青蛙的约会
题目:两只青蛙在网上相识了,它们聊得很开心,于是觉得很有必要见一面。它们很高兴地发现它们住在同一条纬度线上,于是它们约定各自朝西跳,直到碰面为止。可是它们出发之前忘记了一件很重要的事情,既没有问清楚对方的特征,也没有约定见面的具体位置。不过青蛙们都是很乐观的,它们觉得只要一直朝着某个方向跳下去,总能碰到对方的。但是除非这两只青蛙在同一时间跳到同一点上,不然是永远都不可能碰面的。为了帮助这两只乐观的青蛙,你被要求写一个程序来判断这两只青蛙是否能够碰面,会在什么时候碰面。
我们把这两只青蛙分别叫做青蛙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"

思路:很容易想到只要满足(x+k * m)%L=(y+k * n)%L,求出k即为跳的次数。对该式进行简单的化简,得到( x - y + k * (m - n) ) % L =0 .又等价于( x -y + k * (m - n ) ) = s * L 。求出满足的最小的x的解即可。该式子形如 ax+by=c ; 其中 c=y-x ; a=m-n ; b=L ; 用拓展欧几里得即可求解ax+by=1,再两边同乘以c即可。
AC代码:

#include <iostream>
//#include <algorithm>
using namespace std;

int ansx,ansy;

long long __gcd(long long a,long long b)
{
	return b ? __gcd(b,a%b):a;
}

void exgcd(long long a,long long b)
{
	if(b==0)
	{
		ansx=1;
		ansy=0;
		return ;
	}
	exgcd(b,a%b);
	long long t=ansx;
	ansx=ansy;
	ansy=t-a/b*ansy;
}

int main()
{
	long long x,y,m,n,l;
	scanf("%lld%lld%lld%lld%lld",&x,&y,&m,&n,&l);
	long long a=m-n,b=l,c=y-x;
	if(c%__gcd(a,b)!=0)
	printf("Impossible");
	else
	{
		exgcd(a,b);
		printf("%lld",((ansx*c/__gcd(a,b))%l+l)%l);
	}		
    return 0;
}

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。

思路:
求B的逆元D。最后原式子等价 (n*D)%mod , 于用exgcd求解即可。

AC代码:

#include<iostream>
using namespace std;
long long x,y;
void exgcd(long long a,long long b)
{
	if(b==0)
	{
		x=1;
		y=0;
		return ;
	}
	exgcd(b,a%b);
	long long t=x;
	x=y;
	y=t-a/b*y;
}
int main()
{
	int T;
	scanf("%d",&T);
	while(T--)
	{
		long long n,m;
		scanf("%lld%lld",&n,&m);
		exgcd(m,9973);
		x=(x%9973+9973)%9973;
		printf("%lld\n",(x*n)%9973);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值