23.12.13紫书前两章阅读思考·数论

目录

#数论基础

#最大公约数Greatest Common Divisor,gcd

欧几里得算法

时间复杂度

使用stein算法优化

#最小公倍数Least Common Multiple,lcm

#中国剩余定理

#乘法逆元(关于取模运算)

1.拓展欧几里得算法

2.费马小定理+快速幂


乘法逆元部分大致参考学习于知乎想做科学家的ACMer 非常好的文章乘法逆元 - 知乎 (zhihu.com)

数论基础

gcd(a,b)=gcd(a,b-a)

gcd(a,b)=gcd(a,b%a)

最大公约数Greatest Common Divisor,gcd
欧几里得算法

递归ver

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

LL gcd(LL a,LL b){
 return b==0?a;gcd(b,a%b);
}

非递归

LL gcd(LL a,LL b){
    while(b!=0){
        int t = a%b;
        a=b;
        b=t;
    }
    return a;
}

时间复杂度

使用stein算法优化

step1:两数均为偶数时将其同时除以2至少一数为奇数为止,记录除掉的所有公因数2的乘积k;

step2:如果仍有一数为偶数,连续除以2直至该数为奇数为止;

step3:用更相减损法(辗转相减法),即GCD(a,b)=GCD(a-b,b),或辗转相除法求出两奇数的最大公约数d;

step4:原来两数的最大公约数即为d*k

如果 m 为偶数 n 为偶数, gcd(m, n) = gcd(m >> 1, n >> 1) << 1;
如果 m 为偶数 n 为奇数, gcd(m, n) = gcd(m >> 1, n);
如果 m 为奇数 n 为偶数, gcd(m, n) = gcd(m, n >> 1);
如果 m 为奇数 n 为奇数, gcd(m, n) = gcd(n, m - n);

#include<stdio.h>
#include<windows.h>
#pragma warning(disable:4996)
int main(){
	//Stein算法
	int a = 0;
	int b = 0;
	printf("请输入两个整数:");
	scanf("%d%d", &a, &b);
	int gcd = 0;
	int k = 1;
	while ((!(a & 1)) && (!(b & 1))){       //step1;
		k <<= 1;                            //用k记录全部公因子2的乘积 ;
		a >>= 1;
		b >>= 1;
	}
	while (!(a & 1))a >>= 1;                //step2;
	while (!(b & 1))b >>= 1;
	if (a<b) a ^= b, b ^= a, a ^= b;        //交换,使a为较大数; 
	while (a != b){                         //step3;
		a -= b;
		if (a<b) a ^= b, b ^= a, a ^= b;
	}
	gcd = k*a;
	printf("最大公约数为:%d\n", gcd);
	system("pause");
	return 0;
}


最小公倍数Least Common Multiple,lcm

lcm(a,b)=a*b/gcd(a,b);

lcm(a,b)=a/gcd(a,b)*b;(防止乘法超INF)

LL gcd(LL a,LL b){
    while(b!=0){
        int t = a%b;
        a=b;
        b=t;
    }
    return a;
}
中国剩余定理

引子:(紫书第二章习题)

相传韩信才智过人,从不直接清点自己军队的人数,只要让士兵先后以三人一排、五人一排、七人一排地变换队形,而他每次只掠一眼队伍的排尾就知道总人数了。输入包含多组数据,每组数据包含3个非负整数a,b,c,表示每种队形排尾的人数(a<3,b<5,c<7),输出总人数的最小值(或报告无解)。已知总人数不小于10,不超过100。

Input
输入包含多组数据,每组数据包含3个非负整数a,b,c,表示每种队形排尾的人数(a<3,b<5,c<7)

Output
输出总人数的最小值(或报告无解)

Sample Input
2 1 6
2 1 3

Sample Output
41
No answer
————————————————
今天刚看这题的时候第一时间只想到了暴力枚举(太菜惹

查了下原来是有关中国剩余定理CRT的

先学习下CRT和数论再来写出提交代码

数论基础

1.若a\equiv b\left ( mod c\right )

(a+b*k)\equiv b(mod c)

2.若a%b=c

则(a*k)%b = k*c%b(k>0)

Mi^(-1)即为

//扩展欧几里得模板
int ex_gcd(int a,int b,int &x,int &y){
    int d;
    if(b == 0){
        x = 1;
        y = 0;
        return a;
    }
    d = ex_gcd(b,a%b,y,x);
    y -= a / b * x;
    return d;
}
int Chinese_Remainder(int a[],int prime[],int len){
    int i,d,R,y,M,m = 1,sum = 0;
    //计算所有除数的积,也就是所有除数的最小公倍数m
    for(i = 0; i < len; i++)
        m *= prime[i];
    //计算符合所有条件的数
    for(i = 0; i < len; i++){
        M = m / prime[i];//计算除去本身的所有除数的积M
        d = ex_gcd(M,prime[i],R,y);
        sum = (sum + R * M * a[i]) % m;
    }
    return (m + sum % m) % m;//满足所有方程的最小解
}
乘法逆元(关于取模运算)

一,模运算的性质
      1.  (a+b)%p=(a%p+b%p)%p

      2.  (a-b)%p=(a%p-b%p)%p

注意:(9-7)%8=(9%8-7%8)%8=-6 ;不是我们要的值,这时要加上p,改为:

      3.  (a-b)%p=((a%p-b%p)%p+p%p

      4.  (a*b)%p=((a%p)*(b%p))%p

      5.  a ^ b % p = ((a % p)^b) % p

二,除法模运算(不能拆分了就)

对于整数 a,与 a 互质的数 b 作为模数,当整数 x 满足 ax mod b ≡ 1 时,称 x 为 a 关于模 b 的逆元,代码表示就是a * x % b == 1;(存在条件是a与b互质)

因为在竞赛中经常遇到模1e9+7的运算,加减乘可以拆分,但是除法拆分中间量未必能整除,因此引入了乘法逆元的概念;

e.g.求3*6/3对7取模,最终答案为6

 若拆开分别求模,则结果变成了4/3,不是我们想要的答案

因而我们引入了乘法逆元X,乘法逆元X满足3*X mod 7≡1,即X=5

我们将/3换成*5

则计算过程变成18mod7*5然后再mod7即20mod7即为6

由此总结:乘法逆元的作用是在求大数a/b mod c的值的时候 a mod c不能被b整除 

因而将/换成*,b换成(b的乘法逆元) 即可方便计算

引入两种算法求乘法逆元

1.拓展欧几里得算法
代码

拓展欧几里得算法即为求 ax+by=k*gcd(a,b)(裴蜀方程)的一组可行整数解(x,y)

typedef long long LL;
LL ExGCD(LL a, LL b, LL &x, LL &y)
{
    // x, y 为引用传参,故最终程序结束后,x,y会被赋值为可行解
    if(b == 0)
    {
        // 递归终点,ax+by=GCD(a,b)的b为0,故方程变为
        // ax=a,则可行解可以是 x=1, y=0
        x = 1, y = 0;
        return a;
    }
    LL d = ExGCD(b, a % b, x, y), t = x;
    x = y, y = t - a / b * x;
    return d;  // 这里返回值是GCD(a,b)的结果,即最大公约数
}
typedef long long LL;
void ExGCD(LL a,LL b,LL& d,LL& x,LL& y)
{
  if(!b){d=a; x=1; y=0; }
  else {ExGCD(b,a%b,d,y,x); y=-x*(a/b);}
}

代码2及下面思考摘自紫书10.1.3 

递归时x和y顺序反了没关系,上面的代码2是求出了一组解(x1,y1),那么该如何求出其他解呢?

任取一组解(x2,y2),则ax1+by1=ax2+by2,a(x1-y1)=b(y2-y1),两边同除gcd(a,b)

得a'(x1-x2)=b'(y2-y1),因此x1-x2一定是b'整数倍,设x1-x2=kb',则y2-y1=ka'

注意到上面的推到并没有用到ax+by右边是什么

因此得到结论:

a,b,c为任意整数,ax+by=c的一组整数解(x0,y0)

则它的任意整数解为(x0+kb',y0-ka') k∈Z   b'=b/gcd(a,b)  a'=a/gcd(a,b)

拓展gcd和求乘法逆元的关系

a、b互素,aX mod b≡1,即aX-bk=1

令X=x,-k=y 即ax+by=1

typedef long long LL;
LL ExGCD(LL a, LL b, LL &x, LL &y)
{
    if(b == 0)
    {
        x = 1, y = 0;
        return a;
    }
    LL d = ExGCD(b, a % b, x, y), t = x;
    x = y, y = t - a / b * x;
    return d; 
}
int ExGcdInv(int a, int b)
{
    int x, y;
    ExGCD(a, b, x, y);
    return x;
}

时间复杂度:大约O(logn)(斐波那契复杂度)。

适用范围:存在逆元即可求,适用于个数不多但模数b很大的时候,最常用、安全的求逆元方式。

2.费马小定理+快速幂取模
费马小定理

  若p为素数,gcd(a,p)=1

                      a^{_{p-1}}1 mod p

                   或a^{p}a mod p

快速幂 二进制

摘自oi wiki

核心:二进制加速计算(非递归ver)

long long int quik_power(int base, int power)
{
	long long int result = 1;
	while (power > 0)           //指数大于0进行指数折半,底数变其平方的操作
	{
		if (power & 1)			//指数为奇数,power & 1这相当于power % 2 == 1
			result *= base;     //分离出当前项并累乘后保存
		power >>= 1;			//指数折半,power >>= 1这相当于power /= 2;
		base *= base;           //底数变其平方
	}
	return result;              //返回最终结果
}

链接文章快速幂算法 超详细教程-CSDN博客

快速幂应用之取模运算
long long int quik_power(int base, int power, int p)
{
	long long int result = 1;   
	while (power > 0)           
	{
		if (power & 1)         							
			result = result * base % p;   
			//根据公式每个项都取余数后在再做累乘
		base = base * base % p ;   
			//根据公式每个项都取余数后在再做平方操作      						
		power >>= 1;         						
	}
			//根据公式在最后的的结果上再来一次取余数
	return result % p;       
}

注意:若模数p为质数 可以应用费马小定理加速快速幂 即a^{b}a^{bmod(p-1)}mod p

计算乘法逆元

long long int quik_powermod(int base, int power, int p)
{
	long long int result = 1;   
	while (power > 0)           
	{
		if (power & 1)         							
			result = result * base % p;   
		base = base * base % p ;      						
		power >>= 1;         						
	}
	return result % p;       
}
 
int FermatInv(int a, int b)
{
    return quik_powermod(a, b - 2, b);
}
3.递归求逆元
LL Inv(LL a, LL b)
{
    if(a == 1) return 1;
    return (b - b / a) * Inv(b % a, b) % b;
}

原理见知乎文章,时间复杂度为O(log b),b为质数!

4.线性求逆元(之后再学)

若求1-n每个数的逆元 则单独求复杂度太高

逆元打表(递推)

逆元(费马小定理、扩展欧几里得、逆元线性打表)-CSDN博客

乘法逆元 - 知乎 (zhihu.com)

数论——乘法逆元_wx612601a56e103的技术博客_51CTO博客

算法竞赛补充知识:乘法逆元学习_技术交流_牛客网 (nowcoder.com)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值