数论小集(2)

来源:互联网 发布:天道总司 知乎 编辑:程序博客网 时间:2024/05/16 19:19

数论小集(进阶篇)

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.模线性方程(简易)

数论最重要的之一!!
一般形式为解axb(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 longusing 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;}

没错,这次大部分都是和取模有关的,取模可是重中之重,难上加难。。反正咸鱼脑子现在都还记不全逆元算法。。。