組合數取模


組合公式

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,p)=1,那么 a^(p-1) 

p是質數時,φ(p)=p-1,那么a^φ(p)≡1(mod p) 

 

乘法逆元 

(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等價。 

怎樣求乘法逆元?

目前只會歐拉定理,留個坑,待填

根據歐拉定理: 假如p是質數,且(b,p)=1,則φ(p)=(p-1),那么 b^(p-1) ≡1(mod p) 。

b*b^(p-2) ≡1(mod p),所以b^(p-2)就是b的逆元,即k=b^(p-2)

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

這里是博主瞎比寫的,正版的在下面↓↓↓

 

歐拉定理 (證明+在求逆元上的應用)

歐拉定理(又稱費馬-歐拉定理):已知a和n為正整數,並且a和p互素,則a^phi(n) ≡ 1(mod n)。

證明:

  設集合Z = {X1, X2, X3, .... , Xphi(n)},其中Xi (i = 1, 2, .. phi(n))表示第i個不大於n與n互質的數。

  考慮集合S = {a*X1(mod n), a*X2(mod n), ... ,a*Xphi(n) (mod n) },則集合Z = S;

  1) 因為a和n互質,Xi和n也互質,所以a*Xi 也與n互質。所以對任意一個Xi,a*Xi (mod n)一定是Z里面的元素;

  2)對於任意Xi, Xj, 如果Xi != Xj,則a*Xi(mod n) != a*Xj(mod n);

  所以S = Z;

  那么 (a*X1*a*X2*...*a*Xphi(n))(mod n) ---------------------------------------------------- (1)

  = (a*X1(mod n)* a*X2(mod n)* ... *a*Xphi(n) (mod n)) (mod n)

  = (X1* X2* X3* .... * Xphi(n)) (mod n) ------------------------------------------------------ (2)

  式(1)整理得 [a^phi(x)  *  (X1* X2* X3* .... * Xphi(n))] (mod n)

  與(2)式一同消去 (X1* X2* X3* .... * Xphi(n)),即得 a^phi(x) ≡ 1 (mod n);

逆元 :(b/a) (mod n)  =  (b * x) (mod n)。 x表示a的逆元。並且 a*x ≡ 1 (mod n)  

因為a^phi(x) ≡ 1 (mod n),所以x可以表示為a^(phi(n) - 1)。

 當n是素數時 phi(n)=n-1, x表示為a^(n-2)。

 

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) 

For non-negative integers m and n and a prime p, the following congruence relationholds:

\binom{m}{n}\equiv\prod_{i=0}^k\binom{m_i}{n_i}\pmod p,表示的是同余,表示兩個數對p取余,余數相同。

where

m=m_kp^k+m_{k-1}p^{k-1}+\cdots +m_1p+m_0,

and

n=n_kp^k+n_{k-1}p^{k-1}+\cdots +n_1p+n_0

are the base p expansions of m and n respectively.

 

將m划分成m0個1,m1個p,m2個p^2,……

將n划分成n0個1,n1個p,n2個p^2……

C(m,n)的總共的取法就是( 在m0個里面取n0個的方案數 * m1個里面取n1個的方案數 * m2個里面取n2個的方案數 ……)

fzu2020

直接用Lucas

#include<cstdio>
#include<iostream>
using namespace std;
#define ll long long
ll n,m,p;
ll pow_m(ll a,ll k,ll p)
{
    ll ans=1;
    ll tmp=a%p;
    while(k)
    {
        if(k&1)ans=ans*tmp%p;
        tmp=tmp*tmp%p;
        k>>=1;
    }
    return ans;
}
ll C(ll n,ll m,ll p)
{
    if(m>n)return 0;
    ll a=1,b=1;
    for(int i=1;i<=m;i++)
    {
        a=a*(n+i-m)%p;
        b=b*i%p;
    }
    return a*pow_m(b,p-2,p)%p;
}
ll Lucas(ll n,ll m,ll p)
{
    ll ans=1;
    while(n&&m)
    {
        ans=ans*C(n%p,m%p,p)%p;
        n/=p;
        m/=p;
    }
    return ans;
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d%d",&n,&m,&p);
        printf("%lld\n",Lucas(n,m,p));
    }
    return 0;
}
View Code

hdu3037

考慮加多一顆樹,這樣的話當加的樹放了k(0<=k<=m)個beans時,原本的n顆樹上放的beans數量之和就等於m-k(<=m),滿足題目的要求 ,也降低了計算的難度。

則題目要求的是a1+a2+......an+an+1=m(0<=ai<=m,1<=i<=n+1)                                                                                            式1

解有多少組。

考慮把問題轉換成,求a1+a2+......an+an+1=m+n+1(1<=ai<=m+1,1<=i<=n+1)                                                                        式2

解有多少組。

因為式1的每組解,對於每個ai,都加上1的話,就是式2的一組解。

對於式2的求解:

考慮有m+n+1個Beans排成一列,則它們中恰好有m+n個間隔,在m+n個間隔中選擇n個各插入一塊木板,則把這些Beans分成n+1部分,每部分的值對應到每個ai,就是式2的一組解。而在m+n個間隔中選擇n個,則是求組合數的問題了,p<=10^5且為質數,則可用Lucas定理求。

p<10^5,要進行階乘預處理,用C2的方法來算組合數,否則會超時

#include<cstdio>
#include<iostream>
using namespace std;
#define ll long long
#define maxn 100010
ll n,m,p;
ll fact[maxn];
ll pow_m(ll a,ll k,ll p)
{
    ll ans=1;
    ll tmp=a%p;
    while(k)
    {
        if(k&1)ans=ans*tmp%p;
        tmp=tmp*tmp%p;
        k>>=1;
    }
    return ans;
}
ll C2(ll n,ll m,ll p)//p<10^5
{
    if(m>n)return 0;
    return fact[n]*pow_m(fact[m]*fact[n-m]%p,p-2,p);
}
ll Lucas(ll n,ll m,ll p)
{
    ll ans=1;
    while(n&&m)
    {
        ans=ans*C2(n%p,m%p,p)%p;
        n/=p;
        m/=p;
    }
    return ans;
}
void init(ll p)
{
    fact[0]=1;
    for(int i=1;i<=p;i++)fact[i]=fact[i-1]*i%p;
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%lld%lld%lld",&n,&m,&p);
        init(p);
        printf("%lld\n",Lucas(n+m,n,p));
    }
    return 0;
}
View Code

nefu625

題意:給出一個n*m大的花園,求出從左上角到右下角的路徑數目(路徑單調)。

先采取暴力分解,然后快速冪即可。分解的思想很巧妙。

非降路徑數。從(a,b)到(m,n)共有C(m+n-a-b,m-a)種.

所以本題的解應該是C(m+n-2,m-1) (數學太差,窩也不知道它是怎么出來的,留坑

  由於C(x,y)=x!/(y!*(x-y)!),這里我們可以將x!分解素因子,並保存記錄下來,同樣的方法記錄后面兩個,由於x!必然能夠整除(y!*(x-y)!),所以后面兩個數有的因子,x!比然有,只需要將他們的因子的指數相加減,就能得到最后結果的素因子分解的情況,然后最后使用快速冪取模,就能得到最后的結果。

注意:如何進行素因子分解?

     首先要打表將所有的素因子求出來,這里有是將n!進行素因子分解,假設想要求出其中有多少個5,這里是有技巧的。

     假設n=200,那么因子5的個數=200/5+40/5+8/5=49,怎么得到的呢?200中5的倍數有40個,這40個數中其中是25的倍數的有8個,所以還能分解出8個5,這8個數中還有一個是125的倍數,還能分解出一個5,就這樣一直循環下去,就能求出指數的值。

#include<cstdio>
#include<vector>
#include<iostream>
using namespace std;
#define ll long long
#define maxn 200010
ll n,m,p;
vector<int>pri;
bool prime[maxn];
void init()
{
    pri.clear();
    prime[0]=prime[1]=false;
    for(int i=2;i<maxn;i++)
    {
        if(!prime[i])
        {
            pri.push_back(i);
            for(int j=i+i;j<maxn;j+=i)
                prime[j]=true;
        }
    }
   // printf("%d %d\n",pri.size(),pri[pri.size()-1]);//測試本題數據n<200000,則只需要17000個素數
}
ll pow_m(ll a,ll k,ll p)
{
    ll ans=1;
    ll tmp=a%p;
    while(k)
    {
        if(k&1)ans=ans*tmp%p;
        tmp=tmp*tmp%p;
        k>>=1;
    }
    return ans;
}
ll work(ll n,ll su)
{
    ll ans=0;
    while(n)
    {
        ans+=n/su;
        n/=su;
    }
    return ans;
}
ll C3(ll n,ll m,ll p)// 0<n,m<10^6, 0<p<10^9
{
    if(m>n)return 0;
    ll ans=1;
    for(int i=0;pri[i]<=n;i++)//如果出現數組越界的錯誤,則說明素數不夠用,init中再多開出一點素數來,或者換個姿勢求素數
    {
        ll x=work(n,pri[i]);
        ll y=work(n-m,pri[i]);
        ll z=work(m,pri[i]);
        ans=ans*pow_m(pri[i],x-(y+z),p)%p;
    }
    return ans;
}
int main()
{
    init();
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%lld%lld%lld",&n,&m,&p);
        printf("%lld\n",C3(n+m-2,m-1,p));
    }
    return 0;
}
View Code

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM