数论小总结

来源:互联网 发布:python argv 编辑:程序博客网 时间:2024/06/05 02:06

欧几里得GCD(求最大公约数):

int gcd(int n,int m){    return (m) ? gcd(m,n%m) : n;}

最小公倍数LCM:

int LCM(int n,int m){    return n / gcd(n,m) * m;}

扩展欧几里德exgcd:
求ax+by=c直线方程的问题。
作用:1、求不定方程。
2、求解模线性方程(线性同余方程)
3、求解模的逆元
对于ax+by=GCD(a,b)这个方程的解,可以继续延伸出许多等式
bx+a%by=GCD(b,a%b);
……………………
a1x+0*y=GCD(a1,0)=a1;
所以x=1;y=0;GCD(a,b)=a1;
可以利用递归一层一层的往上推x和y的值。
现在推一层与一层之间的关系:
ax1+by1=GCD(a,b);
bx2+a%by2=GCD(b,a%b)=GCD(a,b);
所以ax1+by1=bx2+a%by2;
又因为a%b=(a-a/b*b);
最后化简合并同类项得ax1+by1=ay2+b(x2-a/b*y2);
所以x1=y2;y1=x2-(a/b)y2;
这样就可以设计递归了

void exgcd(int a,int b,int &d,int &x,int &y){    if(!b){        d = a;        x = 1;        y = 0;    }    else{        exgcd(b,a%b,d,y,x);        y -= x*(a/b);    }}

那ax+by=c的整数解;
如果c%gcd(a,b)==0,说明有整数解,反之,没有整数解
x0,y0是ax+by=gcd(a,b)的一个整数解;
在ax+by=c方程中两边除以gcd(a,b),令d = gcd(a,b)得a/d*x+b/d*y=c/d;
ax+by=gcd(a,b)做同样的操作就变成了a/d*x0+b/d*y0=1;
所以x=x0*(c/d); y=y0*(c/d);

对于ax+by=gcd(a,b),则ax1+by1=ax2+by2; a(x1-x2)=b(y2-y1);
对左右均除以gcd(a,b),则a’=a/gcd(a,b),b’=b/gcd(a,b);
所以a’(x1-x2)=b’(y2-y1),a’与b’互质,所以x1-x2是b’的倍数,y2-y1是a’的倍数,设同为k
得到通解x=x1+b/gcd(a,b)*k;
y=y1-a/gcd(a,b)*k;

整除的性质:
1,如果a|b,且b|c,则a|c
2,a|b且b|c,那么a|c
3,设m!=0,那么a|b等价于(m*a)|(m*b)
4,设整数x和y满足下式,a*x+b*y=1,且a|n,b|n,那么(a*b)|n
根据性质3可得,(a*b)|(n*b),(a*b)|(n*a),
根据性质4可得,(a*b)|x*(n*a)+y*(n*b)
化简上式,(a*b)|n*(a*x+b*y)=>(a*b)|n*(1),证毕
5,若b=q*d+c,那么d|b的充要条件是d|c

素数专题:
判断素数:他的因子大小不超过sqrt(N);
互素性质:
a与b,c同时互素 ==> a与b*c互素;
p为素数且p|a*b ==> p|a or p|b;
a*b|c and gcd(a.c)=1 ==>b|c;
gcd(a^k,b)=1(k>=1) ==> gcd(a,b)=1;
a=1(mod b) ⇒ gcd(a,b)=1;
筛素数:
1、起初用2*i,3*i,3*i,4*i,……n*i,有大量重复计算
2、后来用i*i,……………n*i;也有重复计算

#define MAX_N 1000010/*//判断该数是否是素数bool notprime[MAX_N];void init(){    memset(notprime,true,sizeof(notprime));    notprime[0]=notprime[1]=false;    int i,j;    for(i=2;i<MAX_N;++i)    {        if(notprime[i]){            if(i*i>MAX_N) break;            for(j=i*i;j<MAX_N;j+=i)            {                notprime[j]=false;            }        }    }}*/

3、最好用prime[1]*i,prime[2]*i,………,素数的倍数
2*2;
2*3,3*3;
2*4;//当3*4,因为i%prime[j],说明i可以再分为更小的质因数,在下面会出现
2*5,3*5,5*5;
2*6;
……….prime[j]*i;
都是最小的质因子乘以一个数,i%prime[j]==0,可以保证是这样

const int MAXN=100010;int prime[MAXN];void getprime(){    memset(prime,0,sizeof(prime));    //prime[0]储存<=MAXN的素数个数    int i,j;    for(i=2;i<MAXN;++i)    {        if(!prime[i]) prime[++prime[0]]=i;//判断是素数,就储存到对应的第几个数组中中        for(j=1;j<=prime[0]&&prime[j]*i<=MAXN;++j)        {            prime[prime[j]*i]=1;            if(i%prime[j]==0) break;            //如果i和prime[j]中的素数相同,就跳出,防止发生重复            //使上式永远都是小素数乘以大素数        }    }}

小工具:
快速幂:
可快速求a^n;
a^(2^p1*2^p2*2^p3…)=a^(2^p1)*a^(2^p2)*a^(2^p3)…….
矩阵快速幂和这类似,只是变成矩阵乘积了;

typedef long long LL;const int p = 1000000007;LL mod(LL n,LL m){    LL x=n;    LL sum=1;    while(m)    {        if(m&1) sum=(sum*x)%p;//取下m二进制最后一位        m>>=1;        x=(x*x)%p;//x,x^2,x^4,x^8....    }    return sum;}

快速乘:

#define MAXN 1000000007long long pow1(int a,int b){    long long ans=0;    while(b)    {        if(b&1) ans=(ans+a)%MAXN;        a=(a+a)%MAXN;        b>>=1;    }    return ans;}

费马小定理:
O(log2N)的复杂度;
当p为素数时,a^p=a(mod p),即a^(p-1)=1(mod p)
注:a大于2,小于p;

约数个数定理:
对于一个数i,可分解为若干质数幂次的乘积,即
i=prime[1]^a*prime[2]^b…….;
约数个数=(a+1)*(b+1)…….;

数学期望:
定义:随机变量X的数学期望Ex就是所有可能的值按概率加和的权;
期望的线性性质:有限个随机变量之和的数学期望等于每个随机变量的数学期望之和。例如:对于
两个随机变量x和y,E(x+y)=Ex+Ey;
全期望公式:类似全概率公式,把所有情况不重复,不遗漏地分成若干类,每类计算数学期望,然后把
这些数学期望按每类的概率加权加和;

递推(推公式):
汉诺塔问题:一般看最后一步是什么情况,建立递推式;
注意*(重要):可以用矩阵乘法来优化递推式,可以用矩阵快速幂来求解。

数学归纳法:(当讨论的对象具有某种递归性质时,可考虑用数学归纳法)
fibonacci数列:数台阶,雌雄兔子;

逆元求法:
1、欧拉定理(费马小定理):若a与m互质,那么有a^F(m)=1(mod m)
适用于m是质数,a^(-1)=a^(F(m)-1)(mod m);F(m)是欧拉函数,F(m)=m-1;
mod_pow(a,m-2,m);

2、适用于gcd(a,m)=1,ax=1(mod m)
ax+my=1;
用exgcd(a,m,d,x,y)求出的x为a的逆元;

3、线性处理逆元
p为质数的情况下成立
假设p=ax+b,b小于i,x大于1,小于p
ax+by=0(mod p);
两边同时乘x^(-1)*b^(-1) 得x^(-1)=-a*b^(-1)(mod p);
x^(-1)=-(p div x)*(p mod x)^(-1);
即 :
a[i]=(p-p/i)*a[p mod i];
a[1]=1;

卢卡斯求组合数:
首先给出这个Lucas定理:
A、B是非负整数,p是质数。AB写成p进制:A=a[n]a[n-1]…a[0],B=b[n]b[n-1]…b[0]。
则组合数C(A,B)与C(a[n],b[n])C(a[n-1],b[n-1])…*C(a[0],b[0]) modp同余
即:Lucas(n,m,p)=c(n%p,m%p)*Lucas(n/p,m/p,p)
以求解n! % p为例,把n分段,每p个一段,每一段求的结果是一样的。但是需要单独处理每一段的末尾p, 2p, …,把p提取出来,会发现剩下的数正好又是(n / p)!,相当于划归成了一个子问题,这样递归求解即可。
这个是单独处理n!的情况,当然C(n,m)就是n!/(m!*(n-m)!),每一个阶乘都用上面的方法处理的话,就是Lucas定理了,注意这儿的p是素数是有必要的。
Lucas最大的数据处理能力是p在10^5左右,不能再大了,hdu 3037就是10^5级别的!
这里用到的工具就是快速幂和费马小定理:

#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>using namespace std;typedef long long LL;#define p 100005int a[p]={0};LL mod(int n,int m){    LL x=n;    LL sum=1;    while(m)    {        if(m&1) sum=(sum*x)%p;        x=(x*x)%p;        m>>=1;    }    return sum;}LL comb(int n,int m){    printf("%d %d\n",a[n],mod(a[m]*a[n-m],p-2));    return (a[n]*mod(a[m]*a[n-m],p-2))%p;}LL Lucas(LL n,LL m){    if(m==0)        return 1;    return Lucas(n/p,m/p)*comb(n%p,m%p)%p;}int main(){    int i,j;    a[1]=1;    for(i=2;i<p-1;++i)    {        a[i]=(i*a[i-1])%p;    }    /*for(i=1;i<=10;++i)    {        cout<<a[i]<<" ";    }    cout<<endl;*/    long long x=Lucas(180,5);    printf("%lld\n",x);    return 0;}

同余理论:
若a,b为两个整数,且它们的差a-b能被某个自然数m所整除,则称a就模m来说同余于b,或者说a和b关于模m同余
记为a=b(mod m)
它意味着,a-b=m*k(k为某一个整数),例如32=2(mod5) k=6
还有一种理解同余的说法,那就是两个数把多余的部分(大于模数的部分)砍掉,剩下的数值一样,那么我们就说这两个数同余
对于整数a,b,c和自然数m,n, 对模m同余具有以下一些性质
1,自反性,a=a(mod m)
2,对称性,若a=b(mod m) 则 b=a(mod m)
3,传递性,若a=b(mod m),b=c(mod m),则a=c(mod m)
4,同加性,若a=b(mod m),则a+c=b+c(mod m)
5,同乘性,若a=b(mod m),则a*c=b*c(mod m)
 若a=b(mod m) c=d(mod m) 则 a*c=b*d(mod m)
6,同幂性,若a=b(mod m),则an=bn(mod m)
7,推论1,a*b mod k=(a mod k)*(b mod k) mod k
8,推论2,若a mod p=x, a mod q=x,p、q互质,则a mod p*q=x
证明,p,q互质则一定 存在整数s,t使得 a=sp+x,a=tq+x
那么显然我们可以得到,sp=tq,因为p,q互质,所以一定存在s=kq
所以a=(kq)p+x=kqp+x,因此 a=x(mod p*q)

求解模方程:ax=b(mod n)(a,b,n已知)
利用同余理论,a=b(mod n)的充要条件是 a-b是n的整数倍
这样上式变为ax-ny=b;
当gcd(a,b)=1时,方程有唯一解,否则,该方程无解。

中国剩余定理:(求解同余方程组)
x=a1(mod m1);
x=a2(mod m2);
………
x=an(mod mn);
我只会用公式:
M=m1*m2*m3*…….*mn;
M1=M/m1;M2=M/m2;………
Mi^(-1)为Mi模mi的逆元;Mi*Mi^(-1)=1(mod mi);
x=(a1*M1*M1^(-1)+a2*M2*M2^(-1)+a3*M3*M3^(-1)+……………+ak*Mk*Mk^(-1))mod M;

高斯消元法:
主元法:
1、为了提高精度,am1尽量小,而a11尽量大;
2、消元时乘上的不是分数,而是另外的什么东西,使得最总能求出整数解,
乘的是LCM/aii,LCM/ami;
上高斯消元的模板:

int a[maxn][maxn];  //增广矩阵int x[maxn];        //解集bool freeX[maxn];   //标记解是否是自由变元int gcd(int a, int b){    return b ? gcd(b, a % b) : a;}int lcm(int a, int b){    return a / gcd(a, b) * b;}//高斯消元解方程组//返回值-2表示有浮点数解,无整数解//返回值-1表示无解,0表示有唯一解,大于0表示有无穷解,返回自由变元个数//有equ个方程,var个变元//增广矩阵行数[0, equ - 1]//增广矩阵列数[0, var]int gauss(int equ, int var){    for (int i = 0; i <= var; i++)    {        x[i] = 0;        freeX[i] = true;    }    //转换为阶梯矩阵    //col表示当前正在处理的这一列    int col = 0;    int row = 0;    //maxR表示当前这个列中元素绝对值最大的行    int maxRow;    for (; row < equ && col < var; row++, col++)    {        //枚举当前正在处理的行        //找到该col列元素绝对值最大的那行与第k行交换        maxRow = row;        for (int i = row + 1; i < equ; i++)        {            if (abs(a[maxRow][col]) < abs(a[i][col]))            {                maxRow = i;            }        }        if (maxRow != row)        {            //与第row行交换            for (int j = row; j < var + 1; j++)            {                swap(a[row][j], a[maxRow][j]);            }        }        if (a[row][col] == 0)        {            //说明该col列第row行以下全是0,处理当前行的下一列            row--;            continue;        }        for (int i = row + 1; i < equ; i++)        {            //枚举要删的行            if (a[i][col] != 0)            {                int LCM = lcm(abs(a[i][col]), abs(a[row][col]));                int ta = LCM / abs(a[i][col]);                int tb = LCM / abs(a[row][col]);                //异号                if (a[i][col] * a[row][col] < 0)                    tb = -tb;                for (int j = col; j < var + 1; j++)                {                    a[i][j] = a[i][j] * ta - a[row][j] * tb;                }            }        }    }//    //1. 无解的情况: 化简的增广阵中存在(0, 0, ..., a)这样的行(a != 0).    for (int i = row; i < equ; i++)    {        if (a[i][col] != 0)        {            return -1;        }    }    // 2. 无穷解的情况: 在var * (var + 1)的增广阵中出现(0, 0, ..., 0)这样的行,即说明没有形成严格的上三角阵.    //                  出现的行数即为自由变元的个数.    if (row < var)    {        // 首先,自由变元有var - k个,即不确定的变元至少有var - k个.        for (int i = row - 1; i >= 0; i--)        {            // 第i行一定不会是(0, 0, ..., 0)的情况,因为这样的行是在第k行到第equ行.            // 同样,第i行一定不会是(0, 0, ..., a), a != 0的情况,这样的无解的.            // freeNum用于判断该行中的不确定的变元的个数,如果超过1个,则无法求解,它们仍然为不确定的变元.            int freeNum = 0;            int freeIndex = 0;            for (int j = 0; j < var; j++)            {                if (a[i][j] != 0 && freeX[j])                {                    freeNum++;                    freeIndex = j;                }            }            if (1 < freeNum)// 无法求解出确定的变元.                continue;            // 说明就只有一个不确定的变元freeIndex,那么可以求解出该变元,且该变元是确定的.            int tmp = a[i][var];            for (int j = 0; j < var; j++)            {                if (a[i][j] != 0 && j != freeIndex)                {                    tmp -= a[i][j] * x[j];                }            }            x[freeIndex] = tmp / a[i][freeIndex];            freeX[freeIndex] = false;        }        return var - row;    }    // 3. 唯一解的情况: 在var * (var + 1)的增广阵中形成严格的上三角阵.    // 计算出Xn-1, Xn-2 ... X0.    for (int i = var - 1; i >= 0; i--)    {        int tmp = a[i][var];        for (int j = i + 1; j < var; j++)        {            if (a[i][j] != 0)            {                tmp -= a[i][j] * x[j];            }        }        if (tmp % a[i][i] != 0)//浮点数            return -2;        x[i] = tmp / a[i][i];    }    return 0;}

高斯消元求解模线性方程组:
要一直对系数矩阵和增广矩阵进行取模操作;
最后回代求解时,用exgcd来求解;

const int MAXN = 307;int a[MAXN][MAXN];int x[MAXN];int gcd(int n,int m){    return (m) ? gcd(m,n%m) : n;}int LCM(int n,int m){    return n / gcd(n,m) * m;}void swap(int &n,int &m){    int t = n;    n = m;    m = t;}void exgcd(int a,int b,int &d,int &x,int &y){    if(!b){        d = a;        x = 1;        y = 0;    }    else{        exgcd(b,a%b,d,y,x);        y -= x*(a/b);    }}//高斯消元法解模线性方程组//在求解的过程中需要一直模7进行求解//模线性方程需要对系数和结果都对模数取模,很坑int gauss(int equ, int var){    for (int i = 0; i <= var; i++)    {        x[i] = 0;    }    //转换为阶梯矩阵    //col表示当前正在处理的这一列    int col = 0;    int row = 0;    //maxR表示当前这个列中元素绝对值最大的行    int maxRow;    for (; row < equ && col < var; row++, col++)    {        //枚举当前正在处理的行        //找到该col列元素绝对值最大的那行与第k行交换        maxRow = row;        for (int i = row + 1; i < equ; i++)        {            if (abs(a[maxRow][col]) < abs(a[i][col]))            {                maxRow = i;            }        }        if (maxRow != row)        {            //与第row行交换            for (int j = row; j < var + 1; j++)            {                swap(a[row][j], a[maxRow][j]);            }        }        if (a[row][col] == 0)        {            //说明该col列第row行以下全是0,处理当前行的下一列            row--;            continue;        }        for (int i = row + 1; i < equ; i++)        {            //枚举要删的行            if (a[i][col] != 0)            {                int lcm = LCM(abs(a[i][col]), abs(a[row][col]));                int ta = lcm / abs(a[i][col]);                int tb = lcm / abs(a[row][col]);                //异号                if (a[i][col] * a[row][col] < 0)                    tb = -tb;                for (int j = col; j < var + 1; j++)                {                     a[i][j] = ( (a[i][j] * ta - a[row][j] * tb) % 7 + 7 ) % 7;                }            }        }    }//    //1. 无解的情况: 化简的增广阵中存在(0, 0, ..., a)这样的行(a != 0).    for (int i = row; i < equ; i++)    {        if (a[i][col] != 0)        {            return -1;        }    }    // 2. 无穷解的情况: 在var * (var + 1)的增广阵中出现(0, 0, ..., 0)这样的行,即说明没有形成严格的上三角阵.    //                  出现的行数即为自由变元的个数.    if (row < var)    {        return 1;    }    // 3. 唯一解的情况: 在var * (var + 1)的增广阵中形成严格的上三角阵.    // 计算出Xn-1, Xn-2 ... X0.    for (int i = var - 1; i >= 0; i--)    {        int tmp = a[i][var];        for (int j = i + 1; j < var; j++)        {            if (a[i][j] != 0)            {                tmp -= a[i][j] * x[j];            }        }        tmp=((tmp%7)+7)%7;        int e,f,d;        exgcd(a[i][i],7,d,e,f);        int k = tmp / d;        e = e * k;        e = ((e % (7 / d)) + (7 / d)) % (7 / d);        x[i] = e;        if(x[i] < 3)            x[i] += 7;    }    return 0;}

欧拉函数:
欧拉函数:对正整数n,欧拉函数是少于或等于n的数中与n互质的数的数目。例如euler(8)=4,因为1,3,5,7均和8互质。
欧拉公式的延伸:一个数的所有质因子之和是euler(n)*n/2。
欧拉定理:对于互质的正整数a和n,有aφ(n) ≡ 1 mod n。
欧拉函数是积性函数——若m,n互质,φ(mn)=φ(m)φ(n)。
若n是质数p的k次幂,φ(n)=p^k-p^(k-1)=(p-1)p^(k-1),因为除了p的倍数外,其他数都跟n互质。
特殊性质:当n为奇数时,φ(2n)=φ(n)
Euler函数表达通式:euler(x)=x(1-1/p1)(1-1/p2)(1-1/p3)(1-1/p4)…(1-1/pn),其中p1,p2……pn为x的所有素因数,x是不为0的整数。euler(1)=1(唯一和1互质的数就是1本身)
求欧拉值的公式的推导:
有上述的积性函数可得
φ(n)=φ(p1^a1*p2^a2*p3^a3………..)
=φ(p1^a1)φ(p2^a2)φ(p3^a3)…………..
=(p1^a1-p1^(a1-1))(p2^a2-p2^(a2-1))…………….
=n*(1-1/p1)(1-1/p2)*……………….

const int MAXN = 100005;int phi[MAXN];//n*(1-1/p1)*(1-1/p2)*(1-1/p3).....*(1-1/pn);int euler_phi(int n){    int m = (int)sqrt(n+0.5);    int ans=n;    for(int i = 2;i <= m; ++i)    {        if(n % i == 0){        ans = ans / i * (i-1);        while(n % i == 0) n /= i;        }    }    if(n>1) ans = ans / n * (n-1);//如果2,3进去就直接跳出循环了,这是防止2,3的特殊情况出现    return ans;}//利用筛素数的思想void phi_table(int n,int *phi){    for(int i = 2;i <= n; ++i)    {        phi[i] = 0;    }    phi[1] = 1;    for(int i = 2; i <= n; ++i)    {        if(!phi[i]){        //素数才可以进来            for(int j = i;j <= n; j += i)            {                if(!phi[j]) phi[j] = j;                phi[j] = phi[j] / i * (i-1);            }        }    }}

排列与组合:
加法定理:做一件事有n个办法,第i个方法有pi种方案;
乘法定理:做一事有n个步骤,第i个步骤有pi种方案
容斥原理:|AUBUC|=|A|+|B|+|C|-|A^B|-|B^C|-|A^C|+|A^B^C|;

有重复元素的全排列:有k个元素,其中第i个元素有ni个,求全排列个数 ,
n1!n2!n3!….x=n!

可重复选择的组合:有n个元素,每个元素可选多项,一共选k个元素,有多少种办法:
C(n-1,n+k-1)=C(k,n+k-1)

经典描述:http://blog.csdn.net/hackbuteer1/article/details/7450250
非常非常经典的卡特兰数(cantalan数):(与栈有关)

  1.一般公式: C_n = 1/(n + 1)*C(2n,n);         另类递归式:h[n]=((4*n-2)/(n+1))* h[n-1]   3.Cn表示长度2n的dyck word的个数。Dyck word是一个有n个X和n个Y组成的字串,       且所有的部分字串皆满足X的个数大于等于Y的个数。       x代表入栈,y代表出栈,入栈数一定要大于出栈数。   4.将上例的X换成左括号,Y换成右括号,Cn表示所有包含n组括号的合法运算式的个      数。

问题大意是用S表示入栈,X表示出栈,那么合法的序列有多少个(S的个数为n)
显然有c(2n, n)个含S,X各n个的序列,剩下的是计算不允许的序列数(它包含正确个数的S和X,但是违背其它条件)。在任何不允许的序列中,定出使得X的个数超过S的个数的第一个X的位置。然后在导致并包括这个X的部分序列中,以S代替所有的X并以X代表所有的S。结果是一个有(n+1)个S和(n-1)个X的序列。反过来,对一垢一种类型的每个序列,我们都能逆转这个过程,而且找出导致它的前一种类型的不允许序列。例如XXSXSSSXXSSS必然来自SSXSXXXXXSSS。这个对应说明,不允许的序列的个数是c(2n, n-1),因此an = c(2n, n) - c(2n, n-1) =1/(n + 1)*C(2n,n) 。

   5.Cn表示所有不同构的含n个分枝结点的满二叉树的个数。(一个有根二叉树是满的当且仅当每个结点都有两个子树或没有子树。)   两个子树代表入栈和出栈。

前几项: 1, 1, 2, 5, 14, 42, 132, 429, 1430, 4862, 16796, 58786, 208012, 742900, 2674440,

如果把0看成入栈操作,1看成出栈操作,就是说给定6个元素,合法的入栈出栈序列有多少个。
这就是catalan数,这里只是用于栈,等价地描述还有,二叉树的枚举、多边形分成三角形的个数、圆括弧插入公式中的方法数,其通项是c(2n, n)/(n+1)。
像多边形分成三角形的个数,二叉树的枚举则是通过h(0)*h(n-1) + h(2)*h(n-2) + …… + h(n-1)h(0)=h(n))得出1/(n + 1)*C(2n,n),虽然结果是一样的,但是推导过程不一样,目前我知道的应用推导卡特兰数只有这两种。