码迷,mamicode.com
首页 > 其他好文 > 详细

组合数取模

时间:2015-04-16 19:40:05      阅读:323      评论:0      收藏:0      [点我收藏+]

标签:

组合公式

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:

技术分享表示的是同余,表示两个数对p取余,余数相同。

where

技术分享

and

技术分享

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\n",pri.size());//测试本题数据n<17000,则只需要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

 

组合数取模

标签:

原文地址:http://www.cnblogs.com/kylehz/p/4428468.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!