您的位置:首页 > 其它

数论知识整理

2017-11-04 09:22 423 查看

逆元:

又称数论倒数,实质为倒数的扩展。

对于取mod,我们知道:

(a + b) % p = (a%p + b%p) %p

(a - b) % p = (a%p - b%p) %p——(a - b) % p = (a%p - b%p + p) %p 防止出现负数

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

但是对于除法类似的规则并不适用(可以自行举例验证)。

那么我们该如何对存在除法操作的算式取mod呢?

这时就需要逆元了。

定义:如果 ax = 1(mod p),那么称a和x互为mod c意义下的逆元。

当且仅当gcd(a,p)==1即a,p互质时,

对于一个数a的逆元,我们用inv(a)表示

这样对于(a / b) % p = (a * inv(a) ) % p = (a % p * inv(a) % p) % p

求解逆元的方法:

费马小定理求逆元:只适用于模数为质数,且模数与欲求逆元的数互质。

详解:http://blog.csdn.net/qq_36693533/article/details/78412892

代码

ll powr(ll a,ll b)
{
if(!b)
return 1;
ll temp=powr(a,b>>1);
temp=temp*temp%mod;
if(b&1)
temp=temp*a%mod;
return temp%mod;
}

inv[i]=powr(i,mod-2);


扩展欧几里得求逆元:只适用于模数与欲求逆元数互质的情况,但对模数是否为质数没有要求。详解见下方扩展欧几里得内容。

代码

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

exgcd(i,p,x,y);
inv[i]=x;


欧拉定理求逆元…只适用于模数与欲求逆元数互质的情况,但对模数是否为质数没有要求…但我不太会。

注意,以上三种求逆元的方式都一个共同的前提——模数与欲求逆元数互质,这也逆元存在的前提。那么对于模数与欲求数不互质的情况呢?一种方法是递推求,这个时候求的东西就不再是逆元了,将在下方介绍。

还有一种方法是用除法取模的公式….通过公式的转化直接避免除法取模。

公式使用条件:b|a即b整除a



证明:

(a/b)mod m = ans ;

a/b = ans + k * m ;

a = ans * b + k * m * b ;

a mod ( bm ) = ans * b ;

a mod ( bm ) / b = ans ;

递推求1-n在模p(p>n且p为奇质数)意义下的逆元:

inv[i] = (p - p/i)* inv[p % i] % p

另外有结论:1-p在mod p意义下的所有逆元对应1-p-1中的所有数。例如当p=7时,1-6的逆元为1 , 4 , 5 , 2 , 3 , 6,对应1-6中的数。

代码

inv[1]=1;
for(int i=2;i<=n;++i)
inv[i]=(p-p/i)*inv[p%i]%p;


最大公因数gcd和最小公倍数lcm:

代码

ll gcd(ll a,ll b)
{
return b==0?a:gcd(b,a%b);
}
ll lcm(ll a,ll b)
{
//return a*b/gcd(a,b);
return a/gcd(a,b)*b;//防止a*b炸ll
}


扩展欧几里得算法exgcd:

代码

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


gcd与exgcd详解请见:

http://blog.csdn.net/qq_36693533/article/details/78360487

补充:exgcd也可以用于求解同余方程:

ax ≡ b(mod p)-> ax = b+kp,k为某一整数。

求解不定方程ax - kp = b即可。

由此我们可以看出exgcd求逆元的方法:

ax ≡ 1(mod p),ax -kp = 1,k为某一整数。

求解不定方程ax - kp = 1的解x为a在mod p意义下的逆元。

斐波那契数列Fib:

又称黄金分割数列,兔子数列。

递推式:f[i]=f[i-1]+f[i-2](n>=2,f[0]=0,f[1]=1)

通项公式:f[i]=1/√5*{[(1+√5)/2]^i - [(1-√5)/2]^i}

Fib数列的前缀和数列认为Fib数列。

随着数列项数的增加,前一项与后一项之比越来越接近与黄金分割比例。

黄金分割比例:

表达式:a:b=(a+b):a = (√5-1):2

近似值为0.618

将杨辉三角左对齐,将同一斜行(左下到右上)的数加起来,由上到下排列即得此数列。

可用矩阵快速幂加速递推过程:

矩阵快速幂(仅需满足结合律便可使用),将数换为矩阵,将乘换为矩乘即可。

对于某一Fib

[a,b] X

[0,1

1,1] = [b,a+b]

则Fib[1000] = [0,1] X

[0,1 ^ 1000

1,1] —— 快速幂求解

杨辉三角与组合数:

二项式系数的几何排列。

基本性质:

1.

如果你的行数和列数从1开始计,那么第n行第m列的数等于C(n-1,m-1),即从n-1个不同元素中取m-1个元素的组合数。

如果从0开始计,那么第n行第m列的数等于C(n,m),即从n个不同元素中取m个元素的组合数。

许多文章都没写明白…还要手推…



2.

每个数字等于上一行的对应位置的数字与对应位置前一位的数字之和,即第n行第m列的数=第n-1行第m-1列的数+第n-1行第m列的数,C(n,m)=C(n-1,m-1)+C(n-1,m)。可由此性质递推整个杨辉三角。

这也是组合数的性质之一。

杨辉三角揭示了组合数的一些性质如下:

1.C(n,m)=C(n-1,m-1)+C(n-1,m)

考虑在n个数中选m个数,可以从前n-1个数中选m个数,或者从前n-1个数中选m-1个数再选第n个数,方案数相加。

2.C(n,m)=C(n,n-m),具有对称性。

n个数中选m个数与选n-m个数(不选m个数)的方案一一对应。

3.C(n,m)=C(n,m-1)*[(n-m+1)/m] (m>0)

数学方法整理,可以递推某一n确定下的组合数。

组合数公式:C(n,m)=n!/[m!/(n-m)!]

对于n和m小于等于3000的情况,可以递推杨辉三角计算组合数,但要注意中间过程会炸unsigned long long …那么对应求特定条件组合数的题目,请考虑其他思路,如对阶乘分解质因数或二维前缀和统计答案等(NOIP2016组合数问题)

对于n和m较大的情况,可以通过组合数公式计算。

计算n的阶乘,费马小定理求解m,n-m阶乘的逆元,中间过程取模最后相乘即可。

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 211111;
const int mod = 1000000007;
int n,m;
int fact
,inv
;
int powmod(int x, int y)
{
if(!y)
return 1;
int ret=powmod(x,y>>1);
if (y&1)
return 1LL*ret*ret%mod*x%mod;
else return 1LL*ret*ret%mod;
}
int main()
{
scanf("%d%d",&n,&m);
fact[0]=1;
inv[0]=1;
for (int i=1;i<=n;i++)
{
fact[i]=1LL*fact[i-1]*i%mod;
inv[i]=powmod(fact[i],mod-2);
}
int ans=1LL*fact
*inv[n-m]%mod*inv[m]%mod;
printf("%d\n",ans);
return 0;
}


排列数:

排列数公式:P(n,m)=n!/[(n-m)!]——部分排列,n个数中选m个数的排列数。

P(n,n)=n!——全排列,规定0!=1。

顺序对组合无影响,对排列有影响,相同元素不同顺序构成不同排列。

错排:

详见:http://blog.csdn.net/qq_36693533/article/details/78209765

质因数分解:

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
int n;
int main()
{
scanf("%d",&n);
int tmp=n;
for(int i=2;i*i<=n;i++)//枚举1-根号n中的质因子
{                      //乘法避免根号误差
if(tmp%i==0)
printf("%d*",i);
for(;tmp%i==0;)
{
tmp/=i;
}
if(tmp==1)
break;
}
if(tmp!=1)//大于根号n的质因子至多一个
printf("%d*",tmp);
printf("\b ");//退一格(\b后至少加1空格)
}


质数判断:

单个O(√n)

代码

bool isprime(int x)
{
if(x<=1)
return 0;//不是质数
for(int i=2;i*i<=x;i++)
{
if(x%i==0)
return 0;
}
return 1;//是质数
}


但以上方法若用于处理多个数字,时间复杂度显然太高。

我们可以事先筛出一定范围内的质数来O(1)判断。

埃拉托斯特尼筛法(埃氏筛):

O(nloglogn)

代码

for(int i=2;i*i<=n;i++)
{
if(!vis[i])
for(int j=i*i;j<=n;j+=i)
vis[j]=1;
}
//标记所有质数的倍数,未被标记的即为质数


欧拉(线性)筛法:

O(n)

代码

for(int i=2;i<n;i++)
{
if(prime[i])
p[tot++]=i;//把质数存起来
for(int j=0;j<tot&&i*p[j]<n;j++)
{
prime[i* p[j]]=0;
if(i%p[j]==0)
break;//保证每个合数被它最小的质因数筛去
}
}


比较推荐埃氏筛,代码比较好写并且时间复杂度上不会比欧拉筛差太多。

另外质数定理告诉我们,1-n之内的质数大概有n/ln(n)个,这个估值较实际值偏小。

区间筛法:

当判断区间左右端点l,r都很大时,先筛2到√r之间的质数,再标记这些质数再l到r之间的倍数。

例题:http://poj.org/problem?id=2689

题目描述

输入区间[l,u],其中l和u为int范围的整数,区间最大为1000000。求出[l,u]中,相邻质数之差最大和最小的质数对。当存在多个时,输出较小的质数对。

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
typedef long long ll;
const ll INF = 1000000000;
ll l,u,tot,cnt,p,cs,cz,ds,dz,minx=INF,maxn=-INF;
ll num[100001],prim[100001];
bool vis[1500001];
void Prime1()//筛出1-50050中的素数
{
memset(vis,0,sizeof(vis));
tot=0;
vis[1]=1;
for(ll i=2;i<=50050;i++)
{
if(!vis[i])
{
num[++tot]=i;
for(ll j=i*i;j<50050;j+=i)
vis[j]=1;
}
}
}
void Prime2()//以已有素数筛出l-u中的素数
{
memset(vis,0,sizeof(vis));
for(ll i=1;i<=tot;i++)
{
p=l/num[i];
while(p<=1)
p++;
for(ll j=num[i]*p;j<=u;j+=num[i])
{
if(j>=l)
vis[j-l]=1;
}
}
if(l==1)//防止把1记为素数
vis[0]=1;
}
int main()
{
Prime1();
while(~scanf("%lld%lld",&l,&u))
{
Prime2();
cnt=0;
minx=INF,maxn=-INF;
for(int i=0;i<=u-l;i++)
{
if(!vis[i])
prim[++cnt]=i+l;
}
if(cnt<=1)
printf("There are no adjacent primes.\n");
else
{
for(int i=1;i<cnt;i++)
{
if(prim[i+1]-prim[i]>maxn)
{
maxn=prim[i+1]-prim[i];
ds=prim[i];
dz=prim[i+1];
}
if(prim[i+1]-prim[i]<minx)
{
minx=prim[i+1]-prim[i];
cs=prim[i];
cz=prim[i+1];
}
}
printf("%lld,%lld are closest, %lld,%lld are most distant.\n",cs,cz,ds,dz);
}
}
return 0;
}


容斥原理:

基本思想:先不考虑重叠的情况,把包含于某内容中的所有对象的数目先计算出来,然后再把计数时重复计算的数目排斥出去,使得计算的结果既无遗漏又无重复。

|A∪B∪C|

=|A∪B| + |C| - |(A∪B)∩ C|

=|A| + |B| + |C| - |A∩B| - |(A∩C)∪(B∩C)|

=|A| + |B| + |C| - |A∩B| - |A∩C| - |B∩C| + |(A∩C)∩(B∩C)|

=|A| + |B| + |C| - |A∩B| - |A∩C| - |B∩C| + |A∩B∩C|

中国剩余定理:

判断一元线性同余方程组是否有满足所有方程的解,并求出此解。

只适用于一个模数的情况。

1.若其模数m1,m2,…mn两两互质(gcd(m1,m2)=1),则对任意整数a1,a2,…an方程必定有解。不存在无解的情况。

我们设M = m1 * m2 * …* mn,Mi = M/mi,ti = Mi^(-1)为Mi mod mi意义下的逆元。

则方程的通解为:K*M + ∑ (ai * ti * Mi),1<=i<=n且K为任意整数

证明过程比较繁琐先不赘述。

2.若模数不为两两互质的情况,我们每次将两个方程合并为一个方程,最后将方程组合并至一个方程——x ≡ a(mod m),这个式子等价于 x = km + a,x - km =a,k为任意整数。exgcd求解。

若我们有x ≡ a1(mod m1),x ≡ a2(mod m2)。

我们设d = gcd(m1,m2),c = a2 - a1,K = c/d * (m1/d)^-1即 m1/d 的在(mod m2/d)意义下的逆元。

则合并后的新式为:x ≡ m1K + a1(mod m1*m2/d)

若合并过程中得到的新式与原有各式冲突则方程无解。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  数论 NOIPRP++