zoukankan      html  css  js  c++  java
  • 组合数取模运算

    参考博客:

    大数量级组合数的快速计算方法,log转化

       组合数取模

      组合数取模2

    组合公式

    c(n,m)=p(n,m)/m!=n!/((n-m)!*m!)

    c(n,m)=c(n,n-m)

    c(n,m)=c(n-1,m)+c(n-1,m-1)

    欧拉定理  (<---自家博客)

    欧拉定理,(也称费马-欧拉定理)是一个关于同余的性质。欧拉定理表明,若n,a为正整数,且n,a互质,则:

    φ(n)表示1~n中与n互质的数的个数

    看一个基本的例子。令a = 3,n = 5,这两个数是互素的。比5小的正整数中与5互素的数有1、2、3和4,所以φ(5)=4(详情见[欧拉函数])。计算:a^{φ(n)} = 3^4 =81,而81= 80 + 1 Ξ 1 (mod 5)

    这个定理可以用来简化幂的模运算。比如计算7^{222}的个位数,实际是求7^{222}被10除的余数。7和10[[互素]],且φ(10)=4。由欧拉定理知7^4Ξ1(mod 10)。所以7^{222}=(7^4)^55*(7^2)Ξ1^{55}*7^2Ξ49Ξ9 (mod 10)。

    后话:欧拉定理其实是费马小定理的推广     

     参考:  欧拉-费马小定理定理(证明及推论)

     费马小定理:

      对于质数p,任意整数a,均满足:ap≡a(mod p)

     #欧拉定理的推论:

      若正整数a,n互质,那么对于任意正整数b,有ab≡ab mod φ(n)(mod n)

    ps: 这个推论可以帮助我们在求幂运算的时候缩小数据范围和计算次数。具体的说:在求乘方运算时,可以先把底数对mod取模,再把指数对b mod φ(n)取模。

      特别的,如果a,mod不互质,且b>φ(n)时,ab≡ab mod φ(n)+ φ(n)(mod n)

    乘法逆元 (目前不会)

    介绍:如果ax≡1 (mod p),且gcd(a,p)=1(a与p互质),则称a关于模p的乘法逆元为x。

    (a/b) mod p=(a*b^(p-2)) mod p  

    条件:p是素数,gcd(b,p)=1,a%b=0

    定义: 满足a*k≡1 (mod p)的k值就是a关于p的乘法逆元。

     (PS:p一定是个素数才能对a有乘法逆元(除1),特别注意:当p是1时,对于任意a,k都为1) 

    为什么要有乘法逆元呢? 

    当我们要求(a/b) mod p的值(a/b一定是个整数),且a很大,无法直接求得a/b的值时,我们就要用到乘法逆元。 

    我们可以通过求b关于p的乘法逆元k,将a乘上k再模p,即(a*k) mod p。 

    其结果与(a/b) mod p等价。 

    接下来进入正题: 

    Lucas定理针对该取值范围较大又不太大的情况 (Lucas定理是用来求 c(n,m) mod p,p为素数的值。)
    定理描述 

    这样将组合数的求解分解为小问题的乘积,下面考虑计算C(ni, mi) %p.

     已知C(n, m) mod p = n!/(m!(n - m)!) mod p。当我们要求(a/b)mod p的值,且b很大,无法直接求得a/b的值时,我们可以转而使用乘法逆元k,将a乘上k再模p,即(a*k) mod p。 其结果与(a/b) mod p等价。

    ACM数论之旅10---大组合数-卢卡斯定理(在下卢卡斯,你是我的Master吗?(。-`ω´-) )

    卢卡斯说:

    C(n, m) % p  =  C(n / p, m / p) * C(n%p, m%p) % p

    对于C(n / p, m / p),如果n / p 还是很大,可以递归下去,一直到世界的尽头

    上代码:

    ll Lucas(ll n, ll m, ll p){
        return m ? Lucas(n/p, m/p, p) * C(n%p, m%p, p) % p : 1;
    }
    

    ....................算了直接给模板吧(Lucas组合数取模运算函数)

     1 ll fact(ll n, ll p){//n的阶乘求余p 
     2     ll ret = 1;
     3      for (ll i = 1; i <= n ; i ++) ret = ret * i % p ;
     4     return ret ;
     5 }
     6 void ex_gcd(ll a, ll b, ll &x, ll &y, ll &d){
     7     if (!b) {d = a, x = 1, y = 0;}
     8     else{
     9         ex_gcd(b, a % b, y, x, d);
    10         y -= x * (a / b);
    11     }
    12 }
    13 ll inv(ll t, ll p){//如果不存在,返回-1 //求逆元 
    14     ll d, x, y;
    15     ex_gcd(t, p, x, y, d);
    16     return d == 1 ? (x % p + p) % p : -1;
    17 }
    18 ll comb(ll n, ll m, ll p){//C(n, m) % p
    19     if (m < 0 || m > n) return 0;
    20     return fact(n, p) * inv(fact(m, p), p) % p * inv(fact(n-m, p), p) % p;
    21 }
    22 
    23 ll Lucas(ll n, ll m, ll p){//Lucas定理求C(n, m) % p 
    24     return m ? Lucas(n/p, m/p, p) * comb(n%p, m%p, p) % p : 1;
    25 }
    Lucas组合数取模运算函数

    关于拓展卢卡斯,也就是卢卡斯的升级版,卢卡斯限定只能取余一个素数,而拓展卢卡斯则没有限定 

    其求解方法如下:

      若不是素数,将p分解质因数,将C(n,m)分别按照Lucas定理中的方法求对p的质因数的模,然后用中国剩余定理合并。

     上模板:

     1 ll exgcd(ll a,ll b,ll &x,ll &y)
     2 {
     3     if(!b){x=1;y=0;return a;}
     4     ll res=exgcd(b,a%b,x,y),t;
     5     t=x;x=y;y=t-a/b*y;
     6     return res;
     7 }
     8 
     9 inline ll power(ll a,ll b,ll mod)
    10 {
    11     ll sm;
    12     for(sm=1;b;b>>=1,a=a*a%mod)if(b&1)
    13         sm=sm*a%mod;
    14     return sm;
    15 }
    16 
    17 ll fac(ll n,ll pi,ll pk)
    18 {
    19     if(!n)return 1;
    20     ll res=1;
    21     for(register ll i=2;i<=pk;++i)
    22         if(i%pi)(res*=i)%=pk;
    23     res=power(res,n/pk,pk);
    24     for(register ll i=2;i<=n%pk;++i)
    25         if(i%pi)(res*=i)%=pk;
    26     return res*fac(n/pi,pi,pk)%pk;
    27 }
    28 
    29 inline ll inv(ll n,ll mod)
    30 {
    31     ll x,y;
    32     exgcd(n,mod,x,y);
    33     return (x+=mod)>mod?x-mod:x;
    34 }
    35 
    36 inline ll CRT(ll b,ll mod,ll p){return b*inv(p/mod,mod)%p*(p/mod)%p;}
    37 
    38 const int MAXN=11;
    39 
    40 static ll w[MAXN];
    41 
    42 inline ll C(ll n,ll m,ll pi,ll pk)
    43 {
    44     ll up=fac(n,pi,pk),d1=fac(m,pi,pk),d2=fac(n-m,pi,pk);
    45     ll k=0;
    46     for(register ll i=n;i;i/=pi)k+=i/pi;
    47     for(register ll i=m;i;i/=pi)k-=i/pi;
    48     for(register ll i=n-m;i;i/=pi)k-=i/pi;
    49     return up*inv(d1,pk)%pk*inv(d2,pk)%pk*power(pi,k,pk)%pk;
    50 }
    51 
    52 inline ll exlucus(ll n,ll m,ll p)//求C(n, m) % p 
    53 {
    54     ll res=0,tmp=p,pk;
    55     static int lim=sqrt(p)+5;
    56     for(register int i=2;i<=lim;++i)if(tmp%i==0)
    57     {
    58         pk=1;while(tmp%i==0)pk*=i,tmp/=i;
    59         (res+=CRT(C(n,m,i,pk),pk,p))%=p;
    60     }
    61     if(tmp>1)(res+=CRT(C(n,m,tmp,tmp),tmp,p))%=p;
    62     return res;
    63 }
    View Code

    附加:快速乘(如果直接乘会爆ll)

    ll mul(ll a, ll b, ll p){//快速乘,计算a*b%p 
        ll ret = 0;
        while(b){
            if(b & 1) ret = (ret + a) % p;
            a = (a + a) % p;
            b >>= 1;
        }
        return ret;
    }
    

     

    实战一下吧

    hdu 5446

    http://acm.hdu.edu.cn/showproblem.php?pid=5446

    题意:

    给你三个数n, m, k

    第二行是k个数,p1,p2,p3...pk

    所有p的值不相同且p都是质数

    求C(n, m) % (p1*p2*p3*...*pk)的值

    范围:1mn1e18,1k10,pi≤1e5,保证p1*p2*p3*...*pk≤1e18

    AC代码:

     1 #include<cstdio>
     2 typedef long long LL;
     3 const int N = 100000 + 5;
     4 LL mul(LL a, LL b, LL p){//快速乘,计算a*b%p 
     5     LL ret = 0;
     6     while(b){
     7         if(b & 1) ret = (ret + a) % p;
     8         a = (a + a) % p;
     9         b >>= 1;
    10     }
    11     return ret;
    12 }
    13 LL fact(int n, LL p){//n的阶乘求余p 
    14     LL ret = 1;
    15      for (int i = 1; i <= n ; i ++) ret = ret * i % p ;
    16     return ret ;
    17 }
    18 void ex_gcd(LL a, LL b, LL &x, LL &y, LL &d){
    19     if (!b) {d = a, x = 1, y = 0;}
    20     else{
    21         ex_gcd(b, a % b, y, x, d);
    22         y -= x * (a / b);
    23     }
    24 }
    25 LL inv(LL t, LL p){//如果不存在,返回-1 
    26     LL d, x, y;
    27     ex_gcd(t, p, x, y, d);
    28     return d == 1 ? (x % p + p) % p : -1;
    29 }
    30 LL comb(int n, int m, LL p){//C(n, m) % p
    31     if (m < 0 || m > n) return 0;
    32     return fact(n, p) * inv(fact(m, p), p) % p * inv(fact(n-m, p), p) % p;
    33 }
    34 LL Lucas(LL n, LL m, int p){
    35         return m ? Lucas(n/p, m/p, p) * comb(n%p, m%p, p) % p : 1;
    36 }
    37 LL china(int n, LL *a, LL *m){//中国剩余定理 
    38     LL M = 1, ret = 0;
    39     for(int i = 0; i < n; i ++) M *= m[i];
    40     for(int i = 0; i < n; i ++){
    41         LL w = M / m[i];
    42         //ret = (ret + w * inv(w, m[i]) * a[i]) % M;//这句写了会WA,用下面那句 
    43         ret = (ret + mul(w * inv(w, m[i]), a[i], M)) % M;
    44         //因为这里直接乘会爆long long ,所以我用快速乘(unsigned long long也是爆掉,除非用高精度)
    45     }
    46     return (ret + M) % M;
    47 }
    48 int main(){
    49     int T, k;
    50     LL n, m, p[15], r[15];
    51     scanf("%d", &T);
    52     while(T--){
    53         scanf("%I64d%I64d%d", &n, &m, &k);
    54         for(int i = 0; i < k; i ++){
    55             scanf("%I64d", &p[i]);
    56             r[i] = Lucas(n, m, p[i]);
    57         }
    58         printf("%I64d
    ", china(k, r, p));
    59     }
    60 }
    View Code

    我们知道题目要求C(n, m) % (p1*p2*p3*...*pk)的值

    其实这个就是中国剩余定理最后算出结果后的最后一步求余

    那C(n, m)相当于以前我们需要用中国剩余定理求的值

    然而C(n, m)太大,我们只好先算出

    C(n, m) % p1 = r1

    C(n, m) % p2 = r2

    C(n, m) % p3 = r3

    .

    .

    .

    C(n, m) % pk = rk

    用Lucas,这些r1,r2,r3...rk可以算出来

    然后又是用中国剩余定理求答案

    附上大佬博客->>>>dalao

    这是巨佬的ACM数论之旅

  • 相关阅读:
    delphiXE7关于android 检测屏幕是否处于关闭状态
    delphiXE7关于android API的使用和检测WIFI状态的问题
    关于Android下Delphi XE7获取通讯录的问题
    多线程操里操作webbrowser的 Frames
    关于游戏引擎
    今天博客开通了
    集合类型-集合
    编程语言
    live Python4笔记
    live Python3笔记
  • 原文地址:https://www.cnblogs.com/liuyongliu/p/10308928.html
Copyright © 2011-2022 走看看