组合数取模

求:

Cnm(modmo)

n,m,mo<109
Pi<109 (不一定为质数)

p不是质数,(〃>皿<),好恶心

根据中国剩余定理,可得:
mo=pkiiPi=pkii
Xai(modpi) 有唯一解时,有性质
Mi=mo/Piti=M1i(modPi)

X=aiMiti(modmo)

很显然,只要算出每一个的 CnmmodPi ,再用中国剩余定理组合起来即可,
Cnm=m!n!(mn)!

对于每一个的(X!),我们可以把它写成 a(pi)b ,这样即可以处理那些对 Pi 没有逆元的数,也可以在约分的时候愉快的把许多的 pi 约掉,
123.....X 中,时 pi 的倍数的数有 X/pi 个,我们把 X/pi ,向下递归的同时,也可以获得 X/pi 个b(乘积中多了 X/pi pi ),
剩下的不是 pi 的倍数的数,我们把它们分成 (123.....(Pi1))((Pi+1)...(2Pi1)).....() (注意这些都是 Pi )
显然,这些数每一组的乘积( modPi )都是一定的,于是搞出一组后就可以用快速幂愉快的全部搞出来,
然后我们又可以愉快的发现,一堆东西可以预处理( Pi 不会太大),
于是。。。。。
愉快的搞定


标在这里:

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
using namespace std;
typedef long long ll;
typedef double db; 
ll read(ll &n)
{
    char ch;int q=1;n=0;
    for(ch=getchar();(ch<'0' || ch>'9')&&(ch!='-');ch=getchar());
    if(ch=='-')q=-1,ch=getchar();
    for(;ch>='0' && ch<='9';ch=getchar())n=n*10+ch-48;
    n*=q;return n;
}
ll P,n,n1,n2,m,ans,mo;
ll p[100][4],p1[100][15000],p0;//0 is p,1 is k,2 is P^k,3 is IE
ll a[10];
struct qww{ll a,k;};
ll exgcd(ll a,ll b,ll &x,ll &y)
{
    if(!b){x=1,y=0;return a;}
    ll zhi=exgcd(b,a%b,x,y);
    ll t=x;x=y,y=t-a/b*y;
    return zhi;
}
ll IE(ll a,ll mo)
{
    ll x=0,y=0;
    exgcd(a,mo,x,y);
    return (x%mo+mo)%mo;
}
ll ksm(ll a,ll w,ll mo)
{
    ll ans=1;
    while(w)
    {if(w%2)(ans*=a)%=mo;(a*=a)%=mo,w>>=1;}
    return ans;
}
qww Ch(ll q,int mn)
{
    qww ans;ans.a=1;ans.k=0;ll mo=p[mn][2];
    if(q<2)return ans;
    ans=Ch(q/p[mn][0],mn);
    ans.k+=q/p[mn][0];
    (ans.a*=p1[mn][q%mo]*ksm(p1[mn][mo-1],q/mo,mo)%mo)%=mo;
    return ans;
}
ll C(ll m,ll n)
{
    ll ans=0;
    fo(i,1,p0)
    {
        qww x,y,z;
        x=Ch(m,i);
        y=Ch(n,i);
        z=Ch(m-n,i);
        x.k-=y.k+z.k;x.a=(x.a*IE(y.a*z.a,p[i][2]))%mo;
        (ans+=x.a*p[i][3]%mo*ksm(p[i][0],x.k,mo)%mo*(mo/p[i][2]))%=mo;
    }
    return ans;
}
int main()
{
    ll q,w,_;
    read(n),read(m),mo=q=read(P);
    for(int i=2;i*i<=q;i++)
        if(!(q%i))
        {
            p[++p0][0]=i,p[p0][2]=1;
            while(!(q%i))q/=i,p[p0][1]++,p[p0][2]*=i;
            p[p0][3]=IE(mo/p[p0][2],p[p0][2]);
            p1[p0][0]=1;
            fo(j,1,p[p0][2]-1)p1[p0][j]=p1[p0][j-1]*((j%p[p0][0])?j:1)%p[p0][2];
        }
    if(q>1)
    {
        p[++p0][0]=p[p0][2]=q,p[p0][1]=1,p[p0][3]=IE(mo/p[p0][2],p[p0][2]);
        p1[p0][0]=1;
        fo(j,1,p[p0][2]-1)p1[p0][j]=p1[p0][j-1]*((j%p[p0][0])?j:1)%p[p0][2];
    }
    printf("%lld\n",C(m,n));
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值