数论小集(进阶篇)
1.唯一分解定理
任意一个大于1的正整数都能被表示成若干个素数的乘积且表示方法是唯一的;整理可以将相同素数的合并;可以得到
公式:n = P1^a1 * P2^a2 * …………* Pn^an(P1 < P2 < ……Pn);
用代码求一个数唯一分解后的式子我们可以先打素数表,在一直用质数去除它就行了。
代码:
void Prime()
{
cnt=0;
memset(vis,0,sizeof(vis));
for(int i=2;i<=N;i++)
{
if(!vis[i])
prime[cnt++]=i;
for(int j=0;j<cnt&&i*prime[j]<=N;j++)
{
vis[prime[j]*i]=1;
if(i%prime[j]==0) break;
}
}
}
void adde(int num)
{
for(int i=0;i<cnt;i++)
{
while(num%pirme[i]==0)
e[i]++,num/=prime[i];
if(num==1) break;
}
}
(例如,e={1,0,2,0,0,…}表示2^1*5^2=50)
至于唯一分解定理的用处:
1.可以证明a*b=gcd(a,b)*lcm*(a,b)(好像没啥用。。。。)
2.可以降低运算的数量级。大家都知道,数论的题一般规模很大,也许答案不会很大,但在求答案过程中乘着乘着就溢出了,所以产生了许多奇技淫巧来解决这种问题,唯一分解定理就是其中一个。特别是在有不能取余的除法时,可以通过质因子的约分得出答案。
例题:UVA10375
题意是求C(p,q)/C(r,s),保留五位小数,自然是用阶乘来求排列,再唯一分解一下,约个分,美滋滋。
代码:
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=10001;
int e[N],vis[N],prime[N];
int a,b,c,d,cnt;
void Prime()
{
cnt=0;
memset(vis,0,sizeof(vis));
for(int i=2;i<=N;i++)
{
if(!vis[i])
prime[cnt++]=i;
for(int j=0;j<cnt&&i*prime[j]<=N;j++)
{
vis[prime[j]*i]=1;
if(i%prime[j]==0) break;
}
}
}
double poww(int ba,int b)
{
double base=1.0*ba,res=1.0;
int flag=0;
if(b<0) b=abs(b),flag=1;
while(b)
{
if(b&1) res*=base;
base*=base;
b>>=1;
}
if(flag) return 1.0/res;
return res;
}
void adde(int num,int d)
{
for(int i=0;i<cnt;i++)
{
while(num%prime[i]==0)
e[prime[i]]+=d,num/=prime[i];
if(num==1) break;
}
}
void addfac(int num,int d)
{
for(int i=2;i<=num;i++)
adde(i,d);
}
int main()
{
//freopen("a.in","r",stdin);
Prime();
while(scanf("%d %d %d %d",&a,&b,&c,&d)==4)
{
memset(e,0,sizeof(e));
addfac(a,1);
addfac(b,-1);
addfac(a-b,-1);
addfac(c,-1);
addfac(d,1);
addfac(c-d,1);
double ans=1.0;
for(int i=0;i<=cnt;i++)
ans*=poww(prime[i],e[prime[i]]);
printf("%.5lf\n",ans);
}
return 0;
}
2.模线性方程(简易)
数论最重要的之一!!
一般形式为解ax≡b(mod n)(注意:“≡”为同余,a≡b(mod n)意为a和b关于模n同余,a-kn=b(k为整数))
则原来的方程可以化为ax-ny=b,这个方程可能有多个解,也有可能无解。而要解决它,没错,就是用扩展欧几里得,所以我们直接用扩欧就可以求出ax0-ny0=gcd(a,n),
此时只需要利用上次讲的两个结论,来判断是否有解,并且将解转化到题目中限制范围(比如说求非负数解啊)
3.逆元
我们在取模运算中,是没有除法的,但是这怎么能阻止数论发展的脚步,所以数论大佬们就发明了逆元!
对于正整数
和
,如果有
,那么把这个同余方程中
的最小正整数解叫做
模
的逆元。
求逆元有以下几种方法:
1.上面的模线性方程就起作用了,既然
,那么ax-my=1,当a与m互质时,那我们可以用扩欧求出x,x便是a关于m的逆元了。
2.费马小定理。费马小定理其实欧拉定理的一个子情况,至于欧拉定理,emm。。。反正带欧拉二字的东西都很厉害
这里只是提一下。咳,回到正题,费马小定理:
假如p是质数,且gcd(a,p)=1,那么 a(p-1)≡1(mod p),即:假如a是整数,p是质数,且a,p互质(即两者只有一个公约数1),那么a的(p-1)次方除以p的余数恒等于1。(摘自百度百科)
则可推得:a^(m-1)≡1(mod m)--->a*a^(m-2)≡1(mod m)--->a^(m-2)≡1/a(mod m)
所以我们只需要用快速幂取模就行了。
3.线性推逆元。这个可以去看这个链接点击打开链接,这里我只给出递推式:
inv[i] = ( MOD - MOD / i ) * inv[MOD%i] % MOD
4.除法取模的通用式:ans=(a/b)%mod=a%(m*b)/b;
证明如下:
a/b=k*m+x(x<m)
a=k*b*m+b*x
a%(b*m)=b*x
a%(b*m)/b=x
证得。
代码:
//b/a=b*inv(a)(mod m)
int poww(int base,int b)
{
int res=1;
while(b)
{
if(b&1) res=res*base%m;
base=base*base%m;
b>>=1;
}
return res;
}
void gcd(int a,int b,int& g,int& x,int &y)
{
if(!b) {g=a;x=1;y=0;}
else {gcd(b,a%b,g,y,x);y-=x*(a/b);}
}
int inv1()//线性递推逆元
{
inv[1]=1;
for(int i=2;i<=n;i++)
inv[i]=(m-m/i)*inv[m%i]%m;
}
int inv2()//扩展欧几里得求逆元
{
int x,y,g;
gcd(a,m,g,x,y);
return x;
}
int inv3()//费马小定理求逆元
{
return poww(a,m-2)%m;
}
int inv4(int a,int b)//取余除法通用法
{
//ans=a/b(mod m)
ans=a%(m*b)/b;
}
以上算法各有所长,也有各有各的缺点,如扩欧和费马小定理只能在m为质数才行。
4.卢卡斯定理
卢卡斯定理用于较大的组合数取模,有:
Lucas定理:我们令
n=sp+q , m=tp+r
.
(q ,r ≤p)p为素数
那么:

一般来讲,使用这个定理一般是在n,m特别大,而p比较小的时候,对于n,m小于p时,我们需要打阶乘表,用组合数的基础算法来求,其余情况在代码实现时只需要一直递归就行了。
代码:
#include<cstdio>
#include<algorithm>
#include<cstring>
#define LL long long
using namespace std;
LL mod,n,m;
LL fac[10001];
void factor(){
fac[0]=1;
for(LL i=1ll;i<=mod;i++)
fac[i]=(fac[i-1]*i)%mod;
}
LL poww(LL base,LL b)
{
LL res=1ll;
while(b)
{
if(b&1) res=(res*base)%mod;
base=(base*base)%mod;
b>>=1;
}
return res;
}
LL C(LL x,LL y){
if(y>x) return 0;
return (fac[x]*poww(fac[y]*fac[x-y],mod-2))%mod;
}
LL lucas(LL x,LL y){
if(y==0) return 1ll;
return (C(x%mod,y%mod)*lucas(x/mod,y/mod))%mod;
}
int main()
{
//freopen("a.in","r",stdin);
scanf("%lld %lld %lld",&n,&m,&mod);
factor();
printf("%lld",lucas(n,m));
return 0;
}
没错,这次大部分都是和取模有关的,取模可是重中之重,难上加难。。反正咸鱼脑子现在都还记不全逆元算法。。。