0
点赞
收藏
分享

微信扫一扫

数论


文章目录

  • ​​数论​​
  • ​​组合数学​​
  • ​​数学​​
  • ​​费马大定理​​
  • ​​贝祖定理:​​
  • ​​欧几里得(gcd)​​
  • ​​STEIN​​
  • ​​扩展欧几里得​​
  • ​​同余问题​​
  • ​​逆元​​
  • ​​费马小定理(a,m必须互质) 求逆元​​
  • ​​费马大定理​​
  • ​​扩展欧几里得求逆元​​
  • ​​欧拉定理求逆元​​
  • ​​欧拉函数​​
  • ​​欧拉定理​​
  • ​​中国剩余定理​​
  • ​​中国剩余定理​​
  • ​​中国剩余定理扩展——求解模数不互质情况下的线性方程组:​​

数论

组合数学

数学

费马大定理

费马大定理:当n>2时 an+bn=cn,无解

  • 当n==2 时 对于 a2+b2==c2;
    来说满足
    当n为奇数时 他们分别为 a, (a2)/2,)a2)/2+1; 例如 5,12,13; 3,4,5;
    当n为偶数时 他们分别为a,(a2)/4-1,(a2)/4+1; 例如 4,3, 5;6 8 10;

贝祖定理:

  • ax+by=m;判断是否有解:如果m是gcd(a,b)的若干倍。则这个式子一定有整数解。否则没有整数解。

欧几里得(gcd)

  • (辗转相除)求法:

int gcd (int a,int b){
whhile(b){
int temp=a%b;
a=b;
b=temp;
}
return a;
}

  • 或者用递归的想法

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

STEIN

stein算法的时间空间复杂度都和欧几里得相同,而且只需要位移和加减求可以实现,在常数方面更为优秀。

原理:

设x,y为非0奇数,有以下结论:

很显然,第4个式子两个奇数相减会出来一个偶数,那么就可以继续往下除二了。

#include<bits/stdc++.h>
using namespace std;

#define LL long long
LL stein(LL a, LL b) {
if(!a)
return b;
if(!b)
return a;
if(!(a & 1) && !(b & 1))
return stein(a >> 1, b >> 1) << 1;
else if(!(a & 1))
return stein(a >> 1, b);
else if(!(b & 1))
return stein(a, b >> 1);
return stein(abs(a - b), min(a, b));
}

int main() {
while(1) {
LL a, b;
cin >> a >> b;
cout << stein(a, b) << "\n";
}
}

扩展欧几里得

  • 求ax+by=c的解,可以用贝祖定理来判断是否有解。
  • 在有解的情况下求解,c=d*gcd(a,b),则方程式可化成
    (a/d)*x+(b/d)*y=gcd(a,b);令m=a/d,n=b/d
    即mx+ny=gcd(m,n);
    求解之后应该得到的是m与n的值
    要求解得x的值,所以x=dm,y=dn;
  • 有解的情况下,求最优解(最小正整数)。
    ax+by=gcd(a,b);
    ==bx+(a-a/bb)y=gcd(b,b%a)
    ==整理得 ay++b(x-a/b
    y)=gcd(a,b);
    即推出递推式
  • x=y
    y=y-a/b*x
    ;
  • 简单的来说,就是一个不断回溯的过程,想让函数一直运行,直到a%b==0时 最后的得到这样一个方程 a*x=gcd(a,b);,即x=1,y0;时成立
    然后一步步的回溯得到x 和y的特解.
  • ==最后返回的是gcd(a,b);这个不要弄错。
  • 如果还是不懂就结合代码看就能帮助理解了,
  • 代码:

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; //把x y变成上一层的
y=x-(a/b)*y;
x=temp;
return r; //得到a b的最大公因数
}

  • 再来一个求特解的代码

#include<iostream>
#include<cstdio>
#include<cmath>
#include <bits/stdc++.h>

using namespace std;

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; //把x y变成上一层的
y=x-(a/b)*y;
x=temp;
return r; //得到a b的最大公因数
}
int main(){
int a,b,c,x,y;
cin>>a>>b>>c;
int m=exgcd(a,b,x,y);//c 表示gcd 而 x y分别是解
if(c%m==0)
cout<<m<<" "<<x*c/m<<" "<<y*c/m<<" "<<endl;
return 0;
}

  • 这个是ax+by=c;输入的是a,b,c;求x,y,m是gcd(a,b);
  • 应该差不多了吧,嘻嘻qwq。

同余问题

  • ax=1%(n);也就是 ax%n==1%n;
    ax=1+kn; k为倍数
    所以只要满足 ax+bn=1的线性解 x,n都满足要求;
    然后根据欧几里得算法,ax+bn=1 (a与b互质时 存在解) 或者 ax+bn=c ,c=t*gcd(a,b) 存在解。

逆元

说明一下 逆元不一定就是平时我们说的a 和 a-1这种;(这是狭义的)
这里说的是模系下的逆元,就是a*b%m=1;就说在m模系下a是b的逆元。

  • 当a,m互质是,若ax=1(mod m),则称x是 a关于模m的逆元,即做z-1.在[0,m)的范围内,逆元是唯一的

费马小定理(a,m必须互质) 求逆元

  • 逆元:am-1=(1modm);
  • 推导过程: am-1=a*am-2=(1mod m);
  • 即am-2就是a的逆元;

费马大定理

n>2时 xn+yn=zn 无解
当n=2时a2+b2=c2
当n是奇数时 解为:a (a2 /2 )-1 (a2/2)+1;
当n是偶数时 解为: a (a2 /4)-1 (a2/4)+1;

扩展欧几里得求逆元

  • ax+my=gcd(a,m); 因为a,m互质 所以gcd(a,m)=1
    即ax+my=1; 用同余定理得 ax(mod m)=(1mod m);
    即x为a的逆元;

欧拉定理求逆元

  • 说欧拉定理求逆元之前,先说一下欧拉函数

欧拉函数

数论_欧几里得算法_06

欧拉定理

  • aφ(m)==1(mod m);
    ==a*aφ(m)-1==1(mod m);
    也就是ax=1(mod m)的形式,所以aφ(m)-1是a的逆元。

中国剩余定理

大佬的博客.orz​

  • 先看一道poj上的题目:​​【poj1006】 Biorhythms​​
  • 题意:
      人自出生起就有体力,情感和智力三个生理周期,分别为23,28和33天。一个周期内有一天为峰值,在这一天,人在对应的方面(体力,情感或智力)表现最好。通常这三个周期的峰值不会是同一天。现在给出三个日期,分别对应于体力,情感,智力出现峰值的日期。然后再给出一个起始日期,要求从这一天开始,算出最少再过多少天后三个峰值同时出现。
  • 分析:
      首先我们要知道,任意两个峰值之间一定相距整数倍的周期。假设一年的第N天达到峰值,则下次达到峰值的时间为N+Tk(T是周期,k是任意正整数)。所以,三个峰值同时出现的那一天(S)应满足
    S=N1+T1∗k1=N2+T2∗k2=N3+T3∗k3
      N1,N2,N3分别为为体力,情感,智力出现峰值的日期, T1,T2,T3分别为体力,情感,智力周期。 我们需要求出k1,k2,k3三个非负整数使上面的等式成立。

想直接求出k1,k2,k3貌似很难,但是我们的目的是求出S, 可以考虑从结果逆推。根据上面的等式,S满足三个要求:除以T1余数为N1,除以T2余数为N2,除以T3余数为N3。这样我们就把问题转化为求一个最小数,该数除以T1余N1,除以T2余N2,除以T3余N3。这就是著名的中国剩余定理,我们的老祖宗在几千年前已经对这个问题想出了一个精妙的解法。依据此解法的算法,时间复杂度可达到O(1)。
/**************************** 分割线*******************************************/

中国剩余定理

在《孙子算经》中有这样一个问题:“今有物不知其数,三三数之剩二(除以3余2),五五数之剩三(除以5余3),七七数之剩二(除以7余2),问物几何?”这个问题称为“孙子问题”,该问题的一般解法国际上称为“中国剩余定理”。具体解法分三步:

  • 找出三个数:从3和5的公倍数中找出被7除余1的最小数15,从3和7的公倍数中找出被5除余1 的最小数21,最后从5和7的公倍数中找出除3余1的最小数70。
    用15乘以2(2为最终结果除以7的余数),用21乘以3(3为最终结果除以5的余数),同理,用70乘以2(2为最终结果除以3的余数),然后把三个乘积相加15∗2+21∗3+70∗2得到和233。
    用233除以3,5,7三个数的最小公倍数105,得到余数23,即233%105=23。这个余数23就是符合条件的最小数。
      就这么简单。我们在感叹神奇的同时不禁想知道古人是如何想到这个方法的,有什么基本的数学依据吗?
  • 我们将“孙子问题”拆分成几个简单的小问题,从零开始,试图揣测古人是如何推导出这个解法的。
  • 首先,我们假设n1是满足除以3余2的一个数,比如2,5,8等等,也就是满足3∗k+2(k>=0)的一个任意数。同样,我们假设n2是满足除以5余3的一个数,n3是满足除以7余2的一个数。
  • 有了前面的假设,我们先从n1这个角度出发,已知n1满足除以3余2,能不能使得n1+n2的和仍然满足除以3余2?进而使得n1+n2+n3的和仍然满足除以3余2?
  • 这就牵涉到一个最基本数学定理,如果有a%b=c,则有(a+k∗b)%b=c(k为非零整数),换句话说,如果一个除法运算的余数为c,那么被除数与k倍的除数相加(或相减)的和(差)再与除数相除,余数不变。这个是很好证明的。
  • 以此定理为依据,如果n2是3的倍数,n1+n2就依然满足除以3余2。同理,如果n3也是3的倍数,那么n1+n2+n3的和就满足除以3余2。这是从n1的角度考虑的,再从n2,n3的角度出发,我们可推导出以下三点:
    为使n1+n2+n3的和满足除以3余2,n2和n3必须是3的倍数。
    为使n1+n2+n3的和满足除以5余3,n1和n3必须是5的倍数。
    为使n1+n2+n3的和满足除以7余2,n1和n2必须是7的倍数。
  • 因此,为使n1+n2+n3的和作为“孙子问题”的一个最终解,需满足:
    n1除以3余2,且是5和7的公倍数。
    n2除以5余3,且是3和7的公倍数。
    n3除以7余2,且是3和5的公倍数。
  • 所以,孙子问题解法的本质是从5和7的公倍数中找一个除以3余2的数n1,从3和7的公倍数中找一个除以5余3的数n2,从3和5的公倍数中找一个除以7余2的数n3,再将三个数相加得到解。在求n1,n2,n3时又用了一个小技巧,以n1为例,并非从5和7的公倍数中直接找一个除以3余2的数,而是先找一个除以3余1的数,再乘以2。也就是先求出5和7的公倍数模3下的逆元,再用逆元去乘余数。
  • 这里又有一个数学公式,如果a%b=c,那么(a∗k)%b=a%b+a%b+…+a%b=c+c+…+c=k∗c(k>0),也就是说,如果一个除法的余数为c,那么被除数的k倍与除数相除的余数为k∗c。展开式中已证明。
  • 最后,我们还要清楚一点,n1+n2+n3只是问题的一个解,并不是最小的解。如何得到最小解?我们只需要从中最大限度的减掉掉3,5,7的公倍数105即可。道理就是前面讲过的定理“如果a%b=c,则有(a−k∗b)%b=c”。所以(n1+n2+n3)%105就是最终的最小解。
  • 这样一来就得到了中国剩余定理的公式:
  • 设正整数
  • 数论_中国剩余定理_07

  • 两两互素,则同余方程组
  • 数论_中国剩余定理_08

  • 有整数解。并且在模
  • 数论_欧几里得算法_09

  • 下的解是唯一的,
  • 解为
  • 数论_#include_10

  • 其中
  • 数论_欧几里得算法_11

  • ,而
  • 数论_欧几里得算法_12

  • 数论_欧几里得算法_13

  • 数论_#include_14

  • 的逆元。

/**************************** 分割线*******************************************/

中国剩余定理扩展——求解模数不互质情况下的线性方程组:

  • 普通的中国剩余定理要求所有mi的互素,那么如果不互素呢,怎么求解同余方程组?
  • 这种情况就采用两两合并的思想,假设要合并如下两个方程:

数论_#include_15

  • 那么得到:

数论_欧几里得算法_16

  • 我们需要求出一个最小的x使它满足:

数论_#include_17

  • 那么x1和x2就要尽可能的小,于是我们用扩展欧几里得算法求出x1的最小正整数解,将它代回a1+m1x1,得到x的一个特解x′,当然也是最小正整数解。
  • 所以x的通解一定是x′加上lcm(m1,m2)∗k,这样才能保证x模m1和m2的余数是a1和a2。由此,我们把这个x′当做新的方程的余数,把lcm(m1,m2)当做新的方程的模数。(这一段是关键)
  • 合并完成:

数论_#include_18

#include <map>//互质情况
#include <queue>
#include <string>
#include<iostream>
#include<stdio.h>
#include<string.h>
#include <algorithm>
#include <math.h>
typedef long long ll;
using namespace std;
const int maxn=2e6+1010;
#define
const int mod=998244353;
const int MOD=10007;
map<ll,ll>mp;

ll n,m,cnt,sum,a[maxn],b[maxn];

inline int read()
{
int x=0;bool t=false;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=true,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return t?-x:x;
}

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,y,x);
y=y-a/b*x;
return d;
}

int main (){
n=read();

cnt=1;

for(int i=1;i<=n;i++)
{
a[i]=read();
b[i]=read();
cnt=a[i]*cnt;
}

for(int i=1;i<=n;i++)
{
ll ans=cnt/a[i];
ll x,y;
exgcd(ans,a[i],x,y);
x=(x%a[i]+a[i])%a[i];
sum=(sum+x*b[i]*ans)%cnt;
}
cout<<sum<<endl;
return 0;
}


举报

相关推荐

0 条评论